Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee69287813 | |||
| f205075d97 | |||
| d570d03307 | |||
| 483e188c37 | |||
| 0f1665c97d | |||
| 7ef77c3160 | |||
| 74fa735004 | |||
| eb61daab43 | |||
| a2f52d98db | |||
| b0f0710b5b | |||
| 421f04fe60 | |||
| 3d90e8b57a | |||
| a1ea11a196 | |||
| f830a8023d | |||
| a36f0dbf02 | |||
| 824aec6322 | |||
| 8e4d52ddd2 | |||
| 240052da3a | |||
| 8d42156383 | |||
| 95d52e4997 | |||
| e439d4bc39 | |||
| 702dd87c41 | |||
| ca2de244d8 | |||
| b594c9566c | |||
| 1865a3b20d | |||
| 924e553664 | |||
| b005016c48 | |||
| c4eff7c743 | |||
| d669708989 | |||
| 90353c2bc5 | |||
| 5c408948f6 | |||
| 63219434d3 | |||
| f596151c42 | |||
| b7b22dc89e | |||
| 0778b04b6b | |||
| c627690011 | |||
| d52496600f | |||
| 1a4a6721a1 | |||
| 124844b9d4 | |||
| c74804ccf5 | |||
| e5f7b3bd32 | |||
| 0705c68b7b | |||
| 82e65c712b | |||
| 3f9ee1c444 |
@@ -30,9 +30,12 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
|
||||
}
|
||||
|
||||
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
|
||||
std::lock_guard lock(this->eventLock);
|
||||
auto listCopy = this->eventHandlers; //Copy for remove while fire
|
||||
for(const auto& entry : listCopy)
|
||||
decltype(this->eventHandlers) handlers{};
|
||||
{
|
||||
std::lock_guard lock(this->eventLock);
|
||||
handlers = this->eventHandlers; //Copy for remove while fire
|
||||
}
|
||||
for(const auto& entry : handlers)
|
||||
entry.second(event);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule git-teaspeak updated: 9a26231c1f...df91da7514
@@ -533,7 +533,7 @@ std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHa
|
||||
|
||||
bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id,
|
||||
const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) {
|
||||
auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
auto upgrade_id = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES"
|
||||
"(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
|
||||
@@ -198,9 +198,15 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
|
||||
}
|
||||
}
|
||||
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
} else {
|
||||
} else {
|
||||
/* shall never happen, by default each server has the default license */
|
||||
response.set_valid(true);
|
||||
}
|
||||
if(pkt.has_memory_valid() && !pkt.memory_valid()) {
|
||||
response.set_invalid_reason("server memory seems to be invalid");
|
||||
response.set_valid(false);
|
||||
logError(LOG_GENERAL, "Server {} has patched license memory!", client->address());
|
||||
}
|
||||
|
||||
if(client->protocol.version == 2) {
|
||||
if(response.valid())
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "license.h"
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient {
|
||||
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
|
||||
public:
|
||||
enum ConnectionState {
|
||||
CONNECTING,
|
||||
|
||||
@@ -41,6 +41,7 @@ message ServerValidation {
|
||||
required bool license_info = 2;
|
||||
optional bytes license = 3;
|
||||
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
|
||||
optional bool memory_valid = 5;
|
||||
}
|
||||
|
||||
message LicenseResponse {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <event.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <shared/include/license/client.h>
|
||||
#include "shared/include/license/client.h"
|
||||
#include "crypt.h"
|
||||
|
||||
@@ -42,11 +43,16 @@ LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversio
|
||||
}
|
||||
|
||||
LicenseServerClient::~LicenseServerClient() {
|
||||
this->close_connection();
|
||||
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
|
||||
{
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
this->cleanup_network_resources(); /* force cleanup ignoring the previous state */
|
||||
if(is_event_loop) this->network.event_dispatch.detach();
|
||||
|
||||
if(this->buffers.read)
|
||||
Buffer::free(this->buffers.read);
|
||||
threads::save_join(this->network.event_dispatch, false);
|
||||
}
|
||||
|
||||
bool LicenseServerClient::start_connection(std::string &error) {
|
||||
@@ -88,14 +94,17 @@ bool LicenseServerClient::start_connection(std::string &error) {
|
||||
this->network.event_base = event_base_new();
|
||||
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->shared_from_this(); /* We're not allowed to delete outself while hading data. This will lead to dangling pointers */
|
||||
client->callback_read(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->shared_from_this(); /* We're not allowed to delete outself while hading data. This will lead to dangling pointers */
|
||||
client->callback_write(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
|
||||
event_dispatch_spawned = true;
|
||||
this->network.event_dispatch = std::thread([&] {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -114,10 +123,6 @@ bool LicenseServerClient::start_connection(std::string &error) {
|
||||
return true;
|
||||
error_cleanup:
|
||||
this->cleanup_network_resources();
|
||||
if(!event_dispatch_spawned) {
|
||||
event_base_free(this->network.event_base);
|
||||
this->network.event_base = nullptr;
|
||||
}
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
return false;
|
||||
}
|
||||
@@ -243,8 +248,11 @@ void LicenseServerClient::callback_write(short events) {
|
||||
}
|
||||
|
||||
if(events & EV_WRITE) {
|
||||
if(this->connection_state == ConnectionState::CONNECTING)
|
||||
if(this->connection_state == ConnectionState::CONNECTING) {
|
||||
this->callback_socket_connected();
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) /* state may change in the callback */
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t written_bytes{0};
|
||||
|
||||
@@ -365,6 +373,8 @@ void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
|
||||
if(buffer_length < header->length + sizeof(protocol::packet_header)) return;
|
||||
|
||||
this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length);
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) return; /* state may change while we're handing the packet */
|
||||
|
||||
buffer_offset += header->length + sizeof(protocol::packet_header);
|
||||
buffer_length -= header->length + sizeof(protocol::packet_header);
|
||||
}
|
||||
|
||||
+1
-1
Submodule music updated: ad24c38923...8e1ce32ae0
+31
-2
@@ -49,6 +49,7 @@ set(SERVER_SOURCE_FILES
|
||||
src/client/voice/VoiceClientCommandHandler.cpp
|
||||
src/client/voice/VoiceClientPacketHandler.cpp
|
||||
src/client/voice/VoiceClientView.cpp
|
||||
src/client/voice/PacketStatistics.cpp
|
||||
src/TS3ServerClientManager.cpp
|
||||
src/VirtualServer.cpp
|
||||
src/TS3ServerHeartbeat.cpp
|
||||
@@ -136,6 +137,13 @@ set(SERVER_SOURCE_FILES
|
||||
src/weblist/WebListManager.cpp
|
||||
src/weblist/TeamSpeakWebClient.cpp
|
||||
|
||||
src/snapshots/permission.cpp
|
||||
src/snapshots/client.cpp
|
||||
src/snapshots/channel.cpp
|
||||
src/snapshots/server.cpp
|
||||
src/snapshots/groups.cpp
|
||||
src/snapshots/deploy.cpp
|
||||
|
||||
src/manager/ConversationManager.cpp
|
||||
src/client/SpeakingClientHandshake.cpp
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
|
||||
@@ -235,7 +243,7 @@ target_link_libraries(PermMapHelper
|
||||
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "4")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "10")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "12")
|
||||
if (BUILD_TYPE_NAME EQUAL OFF)
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "beta")
|
||||
elseif (BUILD_TYPE_NAME STREQUAL "")
|
||||
@@ -307,4 +315,25 @@ if (NOT DISABLE_JEMALLOC)
|
||||
jemalloc
|
||||
)
|
||||
add_definitions(-DHAVE_JEMALLOC)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
add_executable(Snapshots-Permissions-Test src/snapshots/permission.cpp tests/snapshots/permission.cpp)
|
||||
target_link_libraries(Snapshots-Permissions-Test PUBLIC
|
||||
TeaSpeak
|
||||
CXXTerminal::static #Static
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
DataPipes::rtc::shared
|
||||
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10
|
||||
)
|
||||
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
|
||||
+30
-10
@@ -129,7 +129,6 @@ int main(int argc, char** argv) {
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
}
|
||||
assert(ts::property::impl::validateUnique());
|
||||
|
||||
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
|
||||
#define HELP_FMT " {} {} | {}"
|
||||
@@ -309,6 +308,32 @@ int main(int argc, char** argv) {
|
||||
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
|
||||
}
|
||||
|
||||
{
|
||||
rlimit rlimit{0, 0};
|
||||
//forum.teaspeak.de/index.php?threads/2570/
|
||||
constexpr auto seek_help_message = "Fore more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/).";
|
||||
if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
|
||||
//prlimit -n4096 -p pid_of_process
|
||||
logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno));
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
} else {
|
||||
const auto original = rlimit.rlim_cur;
|
||||
rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384));
|
||||
if(original != rlimit.rlim_cur) {
|
||||
if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
|
||||
logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno));
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
goto rlimit_updates;
|
||||
}
|
||||
}
|
||||
if(rlimit.rlim_cur < 16384) {
|
||||
logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur);
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
}
|
||||
}
|
||||
rlimit_updates:;
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
|
||||
logMessage(LOG_GENERAL, "Starting music providers");
|
||||
|
||||
@@ -371,18 +396,13 @@ int main(int argc, char** argv) {
|
||||
auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password");
|
||||
if(!password.empty()) {
|
||||
logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password);
|
||||
auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid());
|
||||
bool found = false;
|
||||
for(const auto& account : accounts) {
|
||||
if(account->bound_server != 0) continue;
|
||||
auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin");
|
||||
if(!account) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
|
||||
} else {
|
||||
if(!serverInstance->getQueryServer()->change_query_password(account, password)) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)");
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if(!found) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#Required libraries:
|
||||
# "libssl.so"
|
||||
# "libcrypto.so"
|
||||
# "libDataPipes.so"
|
||||
# "libDataPipes-Rtc-Shared.so"
|
||||
# "libjemalloc.so.2"
|
||||
# "libsqlite3.so.0"
|
||||
# "libTeaMusic.so"
|
||||
@@ -50,8 +50,8 @@ query_system_link "libcrypto.so.1.1"
|
||||
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; exit 1; }
|
||||
|
||||
# Setting up DataPipes
|
||||
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-RTC.so")
|
||||
cp "$library_path" . || { echo "failed to copy libDataPipes-RTC.so"; exit 1; }
|
||||
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-Rtc-Shared.so")
|
||||
cp "$library_path" . || { echo "failed to copy libDataPipes-Rtc-Shared.so"; exit 1; }
|
||||
_dp_path="$library_path"
|
||||
|
||||
# Setting up Sqlite3
|
||||
|
||||
@@ -52,8 +52,16 @@ bool config::server::badges::allow_overwolf;
|
||||
bool config::server::authentication::name;
|
||||
|
||||
bool config::server::clients::teamspeak;
|
||||
std::string config::server::clients::extra_welcome_message_teamspeak;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
|
||||
|
||||
bool config::server::clients::teaweb;
|
||||
std::string config::server::clients::extra_welcome_message_teaweb;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
|
||||
|
||||
bool config::server::clients::teaspeak;
|
||||
std::string config::server::clients::extra_welcome_message_teaspeak;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
|
||||
|
||||
uint16_t config::voice::default_voice_port;
|
||||
size_t config::voice::DefaultPuzzlePrecomputeSize;
|
||||
@@ -509,7 +517,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
}
|
||||
|
||||
auto currentVersion = strobf("TeaSpeak ").string() + build::version()->string(true);
|
||||
auto currentVersion = config::server::default_version();
|
||||
if(currentVersion != config::server::DefaultServerVersion) {
|
||||
auto ref = config::server::DefaultServerVersion;
|
||||
try {
|
||||
@@ -1317,13 +1325,60 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
}
|
||||
{
|
||||
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
|
||||
BIND_GROUP(clients);
|
||||
|
||||
/* TeamSpeak */
|
||||
{
|
||||
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teamspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teamspeak_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teamspeak_message_type", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
/* TeaSpeak */
|
||||
/*
|
||||
{
|
||||
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
*/
|
||||
config::server::clients::teaspeak = true;
|
||||
{
|
||||
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
|
||||
/* TeaWeb */
|
||||
{
|
||||
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaweb, true);
|
||||
@@ -1331,9 +1386,18 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
|
||||
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#undef byte
|
||||
#endif
|
||||
#include <spdlog/common.h>
|
||||
#include <misc/strobf.h>
|
||||
#include "geo/GeoLocation.h"
|
||||
#include "../../license/shared/include/license/license.h"
|
||||
#include "build.h"
|
||||
|
||||
namespace YAML {
|
||||
class Node;
|
||||
@@ -86,12 +88,33 @@ namespace ts::config {
|
||||
}
|
||||
|
||||
namespace clients {
|
||||
enum WelcomeMessageType {
|
||||
WELCOME_MESSAGE_TYPE_MIN,
|
||||
WELCOME_MESSAGE_TYPE_NONE = WELCOME_MESSAGE_TYPE_MIN,
|
||||
WELCOME_MESSAGE_TYPE_CHAT,
|
||||
WELCOME_MESSAGE_TYPE_POKE,
|
||||
WELCOME_MESSAGE_TYPE_MAX
|
||||
};
|
||||
|
||||
extern bool teamspeak;
|
||||
extern std::string extra_welcome_message_teamspeak;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teamspeak;
|
||||
|
||||
extern bool teaspeak;
|
||||
extern std::string extra_welcome_message_teaspeak;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teaspeak;
|
||||
|
||||
extern bool teaweb;
|
||||
extern std::string extra_welcome_message_teaweb;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teaweb;
|
||||
}
|
||||
|
||||
extern ssize_t max_virtual_server;
|
||||
|
||||
__attribute__((always_inline)) inline std::string default_version() { return strobf("TeaSpeak ").string() + build::version()->string(true); }
|
||||
__attribute__((always_inline)) inline bool check_server_version_with_license() {
|
||||
return default_version() == DefaultServerVersion || (license->isPremium() && license->isValid());
|
||||
}
|
||||
}
|
||||
|
||||
namespace voice {
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
//
|
||||
|
||||
#include <misc/memtracker.h>
|
||||
|
||||
#include <utility>
|
||||
#include "ConnectionStatistics.h"
|
||||
#include "VirtualServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -13,321 +14,98 @@ using namespace ts::server;
|
||||
using namespace ts::stats;
|
||||
using namespace ts::protocol;
|
||||
|
||||
ConnectionStatistics::ConnectionStatistics(const shared_ptr<ConnectionStatistics>& handle, bool properties) : handle(handle) {
|
||||
ConnectionStatistics::ConnectionStatistics(shared_ptr<ConnectionStatistics> handle) : handle(std::move(handle)) {
|
||||
memtrack::allocated<ConnectionStatistics>(this);
|
||||
|
||||
if(properties) {
|
||||
this->properties = make_shared<Properties>(); //TODO load etc?
|
||||
this->properties->register_property_type<property::ConnectionProperties>();
|
||||
}
|
||||
|
||||
/*
|
||||
this->properties->registerProperty("connection_packets_sent_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_speech", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_keepalive", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_control", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_total", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_bandwidth_sent_last_second_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_sent_last_minute_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_received_last_second_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_received_last_minute_total", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_filetransfer_bandwidth_sent", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bandwidth_received", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bytes_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bytes_received_total", 0, PROP_STATISTIC);
|
||||
*/
|
||||
}
|
||||
|
||||
ConnectionStatistics::~ConnectionStatistics() {
|
||||
memtrack::freed<ConnectionStatistics>(this);
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
|
||||
for(auto entry : this->history_incoming)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
for(auto entry : this->history_file_incoming)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_incoming.clear();
|
||||
this->history_file_incoming.clear();
|
||||
}
|
||||
{
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
for(auto entry : this->history_outgoing)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
for(auto entry : this->history_file_outgoing)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_outgoing.clear();
|
||||
this->history_file_outgoing.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> ConnectionStatistics::statistics() {
|
||||
return this->properties;
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logIncomingPacket(const category::value &category, size_t size) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = uint16_t(size);
|
||||
assert(category >= 0 && category <= 2);
|
||||
this->statistics_second_current.connection_bytes_received[category] += size;
|
||||
this->statistics_second_current.connection_packets_received[category] += 1;
|
||||
|
||||
this->_log_incoming_packet(info_entry, category);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_incoming_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
|
||||
if(index >= 0 && index <= 3) {
|
||||
this->connection_packets_received[index] ++;
|
||||
this->connection_bytes_received[index] += info_entry->size;
|
||||
}
|
||||
this->connection_packets_received[0] ++;
|
||||
this->connection_bytes_received[0] += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
this->history_incoming.push_back(info_entry);
|
||||
}
|
||||
if(this->handle)
|
||||
this->handle->_log_incoming_packet(info_entry, index);
|
||||
this->handle->logIncomingPacket(category, size);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logOutgoingPacket(const category::value &category, size_t size) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = uint16_t(size);
|
||||
assert(category >= 0 && category <= 2);
|
||||
this->statistics_second_current.connection_bytes_sent[category] += size;
|
||||
this->statistics_second_current.connection_packets_sent[category] += 1;
|
||||
|
||||
this->_log_outgoing_packet(info_entry, category);
|
||||
}
|
||||
|
||||
|
||||
void ConnectionStatistics::_log_outgoing_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
|
||||
if(index >= 0 && index <= 3) {
|
||||
this->connection_packets_sent[index] ++;
|
||||
this->connection_bytes_sent[index] += info_entry->size;
|
||||
}
|
||||
this->connection_packets_sent[0] ++;
|
||||
this->connection_bytes_sent[0] += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
this->history_outgoing.push_back(info_entry);
|
||||
}
|
||||
if(this->handle)
|
||||
this->handle->_log_outgoing_packet(info_entry, index);
|
||||
this->handle->logOutgoingPacket(category, size);
|
||||
}
|
||||
|
||||
/* file transfer */
|
||||
void ConnectionStatistics::logFileTransferIn(uint64_t bytes) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = bytes;
|
||||
|
||||
this->_log_incoming_file_packet(info_entry);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_incoming_file_packet(ts::stats::StatisticEntry *info_entry) {
|
||||
this->file_bytes_received += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
this->history_file_incoming.push_back(info_entry);
|
||||
}
|
||||
void ConnectionStatistics::logFileTransferIn(uint32_t bytes) {
|
||||
this->statistics_second_current.file_bytes_received += bytes;
|
||||
this->file_bytes_received += bytes;
|
||||
|
||||
if(this->handle)
|
||||
this->handle->_log_incoming_file_packet(info_entry);
|
||||
this->handle->logFileTransferIn(bytes);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logFileTransferOut(uint64_t bytes) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = bytes;
|
||||
|
||||
this->_log_outgoing_file_packet(info_entry);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_outgoing_file_packet(ts::stats::StatisticEntry *info_entry) {
|
||||
this->file_bytes_sent += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
this->history_file_outgoing.push_back(info_entry);
|
||||
}
|
||||
void ConnectionStatistics::logFileTransferOut(uint32_t bytes) {
|
||||
this->statistics_second_current.file_bytes_sent += bytes;
|
||||
this->file_bytes_sent += bytes;
|
||||
|
||||
if(this->handle)
|
||||
this->handle->_log_outgoing_file_packet(info_entry);
|
||||
this->handle->logFileTransferOut(bytes);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::tick() {
|
||||
StatisticEntry* entry;
|
||||
{
|
||||
auto timeout_min = system_clock::now() - minutes(1);
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_difference = this->last_second_tick.time_since_epoch().count() > 0 ? now - this->last_second_tick : std::chrono::seconds{1};
|
||||
if(time_difference >= std::chrono::seconds{1}) {
|
||||
BandwidthEntry<uint32_t> current{};
|
||||
current.atomic_exchange(this->statistics_second_current);
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
auto period_ms = std::chrono::floor<std::chrono::milliseconds>(time_difference).count();
|
||||
auto current_normalized = current.mul<long double>(1000.0 / period_ms);
|
||||
|
||||
while(!this->history_incoming.empty() && (entry = this->history_incoming[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
this->statistics_second = this->statistics_second.mul<long double>(.2) + current_normalized.mul<long double>(.8);
|
||||
this->total_statistics += current;
|
||||
|
||||
this->history_incoming.pop_front();
|
||||
}
|
||||
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
if(statistics_minute_offset == 0)
|
||||
statistics_minute_offset = current_second;
|
||||
|
||||
while(!this->history_file_incoming.empty() && (entry = this->history_file_incoming[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_file_incoming.pop_front();
|
||||
}
|
||||
}
|
||||
{
|
||||
auto timeout_min = system_clock::now() - minutes(1);
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
while(!this->history_outgoing.empty() && (entry = this->history_outgoing[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_outgoing.pop_front();
|
||||
}
|
||||
|
||||
while(!this->history_file_outgoing.empty() && (entry = this->history_file_outgoing[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_file_outgoing.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
if(this->properties) {
|
||||
auto& _properties = *this->properties;
|
||||
#define M(type, index) \
|
||||
_properties[property::CONNECTION_BYTES_SENT_ ##type] = (uint64_t) this->connection_bytes_sent[index]; \
|
||||
_properties[property::CONNECTION_PACKETS_SENT_ ##type] = (uint64_t) this->connection_packets_sent[index]; \
|
||||
_properties[property::CONNECTION_BYTES_RECEIVED_ ##type] = (uint64_t) this->connection_bytes_received[index]; \
|
||||
_properties[property::CONNECTION_PACKETS_RECEIVED_ ##type] = (uint64_t) this->connection_packets_received[index]; \
|
||||
|
||||
M(TOTAL, 0);
|
||||
M(CONTROL, 1);
|
||||
M(KEEPALIVE, 2);
|
||||
M(SPEECH, 3);
|
||||
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
|
||||
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
|
||||
/* fill all "lost" with the current bandwidth as well */
|
||||
while(statistics_minute_offset <= current_second)
|
||||
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
|
||||
this->last_second_tick = now;
|
||||
}
|
||||
}
|
||||
|
||||
DataSummery ConnectionStatistics::dataReport() {
|
||||
DataSummery report{};
|
||||
auto minTimeout = system_clock::now() - seconds(1);
|
||||
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
|
||||
for(const auto& elm : this->history_incoming){
|
||||
if(elm->timestamp >= minTimeout) {
|
||||
report.recv_second += elm->size;
|
||||
}
|
||||
|
||||
report.recv_minute += elm->size;
|
||||
}
|
||||
|
||||
for(const auto& elm : this->history_file_incoming) {
|
||||
report.file_recv += elm->size;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
for(const auto& elm : this->history_outgoing){
|
||||
if(elm->timestamp >= minTimeout) {
|
||||
report.send_second += elm->size;
|
||||
}
|
||||
|
||||
report.send_minute += elm->size;
|
||||
}
|
||||
|
||||
for(const auto& elm : this->history_file_outgoing) {
|
||||
report.file_send += elm->size;
|
||||
}
|
||||
}
|
||||
|
||||
report.recv_minute /= 60;
|
||||
report.send_minute /= 60;
|
||||
return report;
|
||||
BandwidthEntry<uint32_t> ConnectionStatistics::minute_stats() const {
|
||||
BandwidthEntry<uint32_t> result{};
|
||||
for(const auto& second : this->statistics_minute)
|
||||
result += second;
|
||||
return result.mul<uint32_t>(1. / (double) this->statistics_minute.size());
|
||||
}
|
||||
|
||||
FullReport ConnectionStatistics::full_report() {
|
||||
FullReport report{};
|
||||
FileTransferStatistics ConnectionStatistics::file_stats() {
|
||||
FileTransferStatistics result{};
|
||||
|
||||
for(size_t index = 0 ; index < 4; index++) {
|
||||
report.connection_bytes_sent[index] = (uint64_t) this->connection_bytes_sent[index];
|
||||
report.connection_packets_sent[index] = (uint64_t) this->connection_packets_sent[index];
|
||||
report.connection_bytes_received[index] = (uint64_t) this->connection_bytes_received[index];
|
||||
report.connection_packets_received[index] = (uint64_t) this->connection_packets_received[index];
|
||||
}
|
||||
result.bytes_received = this->file_bytes_received;
|
||||
result.bytes_sent = this->file_bytes_sent;
|
||||
|
||||
report.file_bytes_sent = this->file_bytes_sent;
|
||||
report.file_bytes_received = this->file_bytes_received;
|
||||
|
||||
return report;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<uint64_t, uint64_t> ConnectionStatistics::mark_file_bytes() {
|
||||
std::pair<uint64_t, uint64_t> result;
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
if(this->mark_file_bytes_received < this->file_bytes_received)
|
||||
result.second = this->file_bytes_received - this->mark_file_bytes_received;
|
||||
this->mark_file_bytes_received = (uint64_t) this->file_bytes_received;
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
if(this->mark_file_bytes_sent < this->file_bytes_sent)
|
||||
result.first = this->file_bytes_sent - this->mark_file_bytes_sent;
|
||||
this->mark_file_bytes_sent = (uint64_t) this->file_bytes_sent;
|
||||
|
||||
@@ -11,41 +11,97 @@ namespace ts {
|
||||
}
|
||||
|
||||
namespace stats {
|
||||
struct StatisticEntry {
|
||||
std::atomic<int8_t> use_count{0};
|
||||
std::chrono::time_point<std::chrono::system_clock> timestamp;
|
||||
uint16_t size = 0;
|
||||
template <typename value_t>
|
||||
struct BandwidthEntry {
|
||||
std::array<value_t, 3> connection_packets_sent{};
|
||||
std::array<value_t, 3> connection_bytes_sent{};
|
||||
std::array<value_t, 3> connection_packets_received{};
|
||||
std::array<value_t, 3> connection_bytes_received{};
|
||||
|
||||
value_t file_bytes_sent{0};
|
||||
value_t file_bytes_received{0};
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry& operator=(const BandwidthEntry<other_type>& other) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] = other.connection_packets_sent[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] = other.connection_bytes_sent[index];
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] = other.connection_packets_received[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] = other.connection_bytes_received[index];
|
||||
|
||||
this->file_bytes_sent = other.file_bytes_sent;
|
||||
this->file_bytes_received = other.file_bytes_received;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename target_t>
|
||||
inline BandwidthEntry<target_t> mul(double factor) const {
|
||||
BandwidthEntry<target_t> result{};
|
||||
result = *this;
|
||||
for(auto& val : result.connection_packets_sent) val *= factor;
|
||||
for(auto& val : result.connection_bytes_sent) val *= factor;
|
||||
for(auto& val : result.connection_packets_received) val *= factor;
|
||||
for(auto& val : result.connection_bytes_received) val *= factor;
|
||||
|
||||
result.file_bytes_sent *= factor;
|
||||
result.file_bytes_received *= factor;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry& operator+=(const BandwidthEntry<other_type>& other) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] += other.connection_packets_sent[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] += other.connection_bytes_sent[index];
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] += other.connection_packets_received[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] += other.connection_bytes_received[index];
|
||||
|
||||
this->file_bytes_sent += other.file_bytes_sent;
|
||||
this->file_bytes_received += other.file_bytes_received;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry operator+(const BandwidthEntry<other_type>& other) {
|
||||
return BandwidthEntry{*this} += other;
|
||||
}
|
||||
|
||||
template <typename atomic_t>
|
||||
inline void atomic_exchange(BandwidthEntry<std::atomic<atomic_t>>& source) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] = source.connection_packets_sent[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] = source.connection_bytes_sent[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] = source.connection_packets_received[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] = source.connection_bytes_received[index].exchange(0);
|
||||
|
||||
this->file_bytes_sent = source.file_bytes_sent.exchange(0);
|
||||
this->file_bytes_received = source.file_bytes_received.exchange(0);
|
||||
}
|
||||
};
|
||||
|
||||
struct DataSummery {
|
||||
uint32_t send_minute;
|
||||
uint32_t send_second;
|
||||
|
||||
uint32_t recv_minute;
|
||||
uint32_t recv_second;
|
||||
|
||||
uint32_t file_recv;
|
||||
uint32_t file_send;
|
||||
};
|
||||
|
||||
struct FullReport {
|
||||
uint64_t connection_packets_sent[4]{0, 0, 0, 0};
|
||||
uint64_t connection_bytes_sent[4]{0, 0, 0, 0};
|
||||
uint64_t connection_packets_received[4]{0, 0, 0, 0};
|
||||
uint64_t connection_bytes_received[4]{0, 0, 0, 0};
|
||||
|
||||
uint64_t file_bytes_sent = 0;
|
||||
uint64_t file_bytes_received = 0;
|
||||
struct FileTransferStatistics {
|
||||
uint64_t bytes_received{0};
|
||||
uint64_t bytes_sent{0};
|
||||
};
|
||||
|
||||
class ConnectionStatistics {
|
||||
public:
|
||||
struct category {
|
||||
/* Only three categories. Map unknown to category 0 */
|
||||
enum value {
|
||||
COMMAND,
|
||||
ACK,
|
||||
KEEP_ALIVE,
|
||||
VOICE,
|
||||
UNKNOWN
|
||||
UNKNOWN = COMMAND
|
||||
};
|
||||
|
||||
constexpr static std::array<category::value, 16> lookup_table{
|
||||
@@ -53,10 +109,10 @@ namespace ts {
|
||||
VOICE, /* VoiceWhisper */
|
||||
COMMAND, /* Command */
|
||||
COMMAND, /* CommandLow */
|
||||
ACK, /* Ping */
|
||||
ACK, /* Pong */
|
||||
ACK, /* Ack */
|
||||
ACK, /* AckLow */
|
||||
KEEP_ALIVE, /* Ping */
|
||||
KEEP_ALIVE, /* Pong */
|
||||
COMMAND, /* Ack */
|
||||
COMMAND, /* AckLow */
|
||||
COMMAND, /* */
|
||||
|
||||
UNKNOWN,
|
||||
@@ -76,58 +132,38 @@ namespace ts {
|
||||
return from_type(type.type());
|
||||
}
|
||||
};
|
||||
explicit ConnectionStatistics(const std::shared_ptr<ConnectionStatistics>& /* root */, bool /* spawn properties */);
|
||||
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
|
||||
~ConnectionStatistics();
|
||||
|
||||
std::shared_ptr<Properties> statistics();
|
||||
|
||||
inline void logIncomingPacket(const protocol::ClientPacket& packet) { this->logIncomingPacket(category::from_type(packet.type()), packet.length()); }
|
||||
void logIncomingPacket(const category::value& /* category */, size_t /* length */);
|
||||
inline void logOutgoingPacket(const protocol::ServerPacket& packet) { this->logOutgoingPacket(category::from_type(packet.type()), packet.length()); }
|
||||
void logOutgoingPacket(const category::value& /* category */, size_t /* length */);
|
||||
void logFileTransferIn(uint64_t);
|
||||
void logFileTransferOut(uint64_t);
|
||||
void logFileTransferIn(uint32_t);
|
||||
void logFileTransferOut(uint32_t);
|
||||
|
||||
void tick();
|
||||
|
||||
DataSummery dataReport();
|
||||
FullReport full_report();
|
||||
[[nodiscard]] inline const BandwidthEntry<uint32_t>& total_stats() const { return this->total_statistics; }
|
||||
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
|
||||
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
|
||||
|
||||
FileTransferStatistics file_stats();
|
||||
std::pair<uint64_t, uint64_t> mark_file_bytes();
|
||||
|
||||
inline bool measure_bandwidths() { return this->_measure_bandwidths; }
|
||||
void measure_bandwidths(bool flag) { this->_measure_bandwidths = flag; }
|
||||
|
||||
inline bool has_properties() { return !!this->properties; }
|
||||
private:
|
||||
bool _measure_bandwidths = true;
|
||||
std::shared_ptr<ConnectionStatistics> handle;
|
||||
std::shared_ptr<Properties> properties;
|
||||
|
||||
BandwidthEntry<uint32_t> total_statistics{};
|
||||
|
||||
std::atomic<uint64_t> connection_packets_sent[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_bytes_sent[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_packets_received[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_bytes_received[4]{0, 0, 0, 0};
|
||||
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
|
||||
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
|
||||
std::array<BandwidthEntry<uint32_t>, 60> statistics_minute{};
|
||||
uint32_t statistics_minute_offset{0}; /* pointing to the upcoming minute */
|
||||
std::chrono::system_clock::time_point last_second_tick{};
|
||||
|
||||
std::atomic<uint64_t> file_bytes_sent = 0;
|
||||
std::atomic<uint64_t> file_bytes_received = 0;
|
||||
std::atomic<uint64_t> file_bytes_sent{0};
|
||||
std::atomic<uint64_t> file_bytes_received{0};
|
||||
|
||||
std::atomic<uint64_t> mark_file_bytes_sent = 0;
|
||||
std::atomic<uint64_t> mark_file_bytes_received = 0;
|
||||
|
||||
spin_lock history_lock_outgoing;
|
||||
spin_lock history_lock_incoming;
|
||||
std::deque<StatisticEntry*> history_file_incoming{};
|
||||
std::deque<StatisticEntry*> history_file_outgoing{};
|
||||
std::deque<StatisticEntry*> history_incoming{};
|
||||
std::deque<StatisticEntry*> history_outgoing{};
|
||||
|
||||
void _log_incoming_packet(StatisticEntry */* statistics */, int8_t /* type index */);
|
||||
void _log_outgoing_packet(StatisticEntry* /* statistics */, int8_t /* type index */);
|
||||
|
||||
void _log_incoming_file_packet(StatisticEntry */* statistics */);
|
||||
void _log_outgoing_file_packet(StatisticEntry* /* statistics */);
|
||||
uint64_t mark_file_bytes_sent{0};
|
||||
uint64_t mark_file_bytes_received{0};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ void DatabaseHelper::tick() {
|
||||
{
|
||||
threads::MutexLock l(this->propsLock);
|
||||
auto pcpy = this->cachedProperties;
|
||||
for(const auto& mgr : pcpy){
|
||||
for(const auto& mgr : pcpy) {
|
||||
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->properties.expired()) {
|
||||
@@ -159,7 +159,12 @@ void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server,
|
||||
//TODO delete complains
|
||||
}
|
||||
|
||||
inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& server, v2::PermissionManager* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
|
||||
inline sql::result load_permissions_v2(
|
||||
const std::shared_ptr<VirtualServer>& server,
|
||||
v2::PermissionManager* manager,
|
||||
sql::command& command,
|
||||
bool test_channel, /* only used for client permissions (client channel permissions) */
|
||||
bool is_channel) {
|
||||
auto start = system_clock::now();
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
@@ -213,7 +218,7 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(channel_id == 0)
|
||||
if(channel_id == 0 || is_channel)
|
||||
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
|
||||
else
|
||||
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
|
||||
@@ -282,7 +287,7 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_USER},
|
||||
variable{":id", cldbid});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true));
|
||||
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true, false));
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +369,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -436,7 +441,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_PLAYLIST},
|
||||
variable{":id", playlist_id});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -502,7 +507,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPe
|
||||
variable{":chid", channel},
|
||||
variable{":id", 0},
|
||||
variable{":type", permission::SQL_PERM_CHANNEL});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, true));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -568,7 +573,9 @@ bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::sh
|
||||
if(!res) return false;
|
||||
|
||||
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
|
||||
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0});
|
||||
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()},
|
||||
variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0},
|
||||
variable{":connections", 0});
|
||||
if(cldbid == 0){ //Completly new user
|
||||
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
@@ -617,8 +624,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
|
||||
}
|
||||
}
|
||||
|
||||
const auto &info = property::impl::info_key(type, key);
|
||||
if(info->name == "undefined") {
|
||||
const auto &info = property::find(type, key);
|
||||
if(info.name == "undefined") {
|
||||
logError(sid, "Found unknown property in database! ({})", key);
|
||||
return 0;
|
||||
}
|
||||
@@ -630,9 +637,9 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
|
||||
prop.setDbReference(true);
|
||||
*/
|
||||
|
||||
auto data = make_unique<FastPropertyEntry>();
|
||||
auto data = std::make_unique<FastPropertyEntry>();
|
||||
data->type = &info;
|
||||
data->value = value;
|
||||
data->type = info;
|
||||
properties.push_back(move(data));
|
||||
return 0;
|
||||
});
|
||||
@@ -705,7 +712,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
|
||||
}
|
||||
|
||||
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
|
||||
logTrace(serverId, "Updating server property: " + std::string{prop.type().name} + ". New value: " + prop.value() + ". Query: " + sql);
|
||||
sql::command(this->sql, sql,
|
||||
variable{":sid", serverId},
|
||||
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
|
||||
@@ -925,7 +932,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
|
||||
if(!prop.isModified()) return;
|
||||
if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
|
||||
return;
|
||||
}
|
||||
if(!prop.get_handle()) return;
|
||||
@@ -940,7 +947,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
}
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
|
||||
sql::command(this->sql, sql,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", prop.type().type_property},
|
||||
@@ -965,7 +972,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
if(query.empty()) return;
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + std::string{prop.type().name} + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
|
||||
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
});
|
||||
|
||||
@@ -1119,8 +1126,8 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}
|
||||
}
|
||||
|
||||
auto info = property::impl::info_key(type, key);
|
||||
if(info == property::PropertyDescription::unknown) {
|
||||
const auto& info = property::find(type, key);
|
||||
if(info.is_undefined()) {
|
||||
logError(serverId, "Invalid property ({} | {})", key, type);
|
||||
return 0;
|
||||
}
|
||||
@@ -1145,7 +1152,7 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}
|
||||
|
||||
auto entry = make_unique<StartupPropertyEntry>();
|
||||
entry->info = info;
|
||||
entry->info = &info;
|
||||
entry->value = value;
|
||||
entry->id = id;
|
||||
entry->type = type;
|
||||
|
||||
@@ -57,8 +57,8 @@ namespace ts {
|
||||
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id = 0;
|
||||
std::shared_ptr<property::PropertyDescription> info = property::PropertyDescription::unknown;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace ts {
|
||||
};
|
||||
|
||||
struct FastPropertyEntry {
|
||||
std::shared_ptr<property::PropertyDescription> type;
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
|
||||
@@ -215,7 +215,6 @@ namespace ts {
|
||||
bool isClientCached(const ClientDbId& /* client database id */);
|
||||
void clearCache();
|
||||
|
||||
|
||||
bool isLocalGroup(std::shared_ptr<Group>);
|
||||
protected:
|
||||
void handleChannelDeleted(const ChannelId& /* channel id */);
|
||||
|
||||
@@ -41,8 +41,7 @@ extern bool mainThreadActive;
|
||||
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
serverInstance = this;
|
||||
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
|
||||
this->statistics->measure_bandwidths(true);
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
|
||||
|
||||
std::string error_message{};
|
||||
this->license_service_ = std::make_shared<license::LicenseService>();
|
||||
@@ -70,8 +69,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto &info = property::impl::info<property::InstanceProperties>(key);
|
||||
if(*info == property::SERVERINSTANCE_UNDEFINED) {
|
||||
const auto &info = property::find<property::InstanceProperties>(key);
|
||||
if(info == property::SERVERINSTANCE_UNDEFINED) {
|
||||
logError(0, "Got an unknown instance property " + key);
|
||||
return 0;
|
||||
}
|
||||
@@ -414,7 +413,7 @@ FwIDAQAB
|
||||
startTimestamp = system_clock::now();
|
||||
this->voiceServerManager->executeAutostart();
|
||||
|
||||
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
|
||||
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds{500});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -482,12 +481,12 @@ void InstanceHandler::tickInstance() {
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
|
||||
//logger::flush();
|
||||
}
|
||||
if(statisticsUpdateTimestamp + seconds(5) < now) {
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
|
||||
this->statistics->tick();
|
||||
}
|
||||
if(statisticsUpdateTimestamp + seconds(1) < now) {
|
||||
statisticsUpdateTimestamp = now;
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
|
||||
this->statistics->tick();
|
||||
}
|
||||
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
|
||||
|
||||
@@ -134,8 +134,8 @@ bool InstanceHandler::setupDefaultGroups() {
|
||||
}
|
||||
|
||||
for(const auto& property : info->properties) {
|
||||
const auto& prop = property::impl::info<property::InstanceProperties>(property);
|
||||
if(*prop == property::SERVERINSTANCE_UNDEFINED) {
|
||||
const auto& prop = property::find<property::InstanceProperties>(property);
|
||||
if(prop.is_undefined()) {
|
||||
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
|
||||
} else {
|
||||
this->properties()[prop] = group->groupId();
|
||||
|
||||
@@ -201,7 +201,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
||||
auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0;
|
||||
debugMessage(0, "Got server snapshot with version {}", snapshot_version);
|
||||
while(true){
|
||||
for(auto &key : arguments[index].keys()){
|
||||
for(const auto &key : arguments[index].keys()){
|
||||
if(key == "end_virtualserver") continue;
|
||||
if(key == "begin_virtualserver") continue;
|
||||
if(snapshot_version == 0) {
|
||||
@@ -559,8 +559,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
||||
if(key == "bot_owner_id") continue;
|
||||
if(key == "bot_id") continue;
|
||||
|
||||
const auto& property = property::info<property::ClientProperties>(key);
|
||||
if(property->property_index == property::CLIENT_UNDEFINED) {
|
||||
const auto& property = property::find<property::ClientProperties>(key);
|
||||
if(property.is_undefined()) {
|
||||
debugMessage(log_server_id, PREFIX + "Failed to parse give music bot property {} for bot {} (old: {}). Value: {}", key, new_bot_id, bot_id, arguments[index][key].string());
|
||||
continue;
|
||||
}
|
||||
@@ -597,8 +597,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
||||
if(key == "begin_playlist") continue;
|
||||
if(key == "playlist_id") continue;
|
||||
|
||||
const auto& property = property::info<property::ClientProperties>(key);
|
||||
if(property->property_index == property::CLIENT_UNDEFINED) {
|
||||
const auto& property = property::find<property::ClientProperties>(key);
|
||||
if(property.is_undefined()) {
|
||||
debugMessage(log_server_id, PREFIX + "Failed to parse given playlist property {} for playlist {} (old: {}). Value: {}", key, playlist_index, playlist_id, arguments[index][key].string());
|
||||
continue;
|
||||
}
|
||||
@@ -787,12 +787,12 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
case property::VIRTUALSERVER_UPLOAD_QUOTA:
|
||||
case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH:
|
||||
case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH:
|
||||
cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save<int64_t>();
|
||||
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_save<int64_t>();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmd[index][serverProperty.type().name] = serverProperty.value();
|
||||
cmd[index][std::string{serverProperty.type().name}] = serverProperty.value();
|
||||
}
|
||||
cmd[index++]["end_virtualserver"] = "";
|
||||
}
|
||||
@@ -807,7 +807,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
else if(channelProperty.type() == property::CHANNEL_PID)
|
||||
cmd[index]["channel_pid"] = channelProperty.as<string>();
|
||||
else
|
||||
cmd[index][channelProperty.type().name] = channelProperty.as<string>();
|
||||
cmd[index][std::string{channelProperty.type().name}] = channelProperty.as<string>();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -899,7 +899,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
|
||||
if(property->value == property->type->default_value) continue;
|
||||
|
||||
cmd[index][property->type->name] = property->value;
|
||||
cmd[index][std::string{property->type->name}] = property->value;
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -931,7 +931,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
|
||||
if(property->value == property->type->default_value) continue;
|
||||
|
||||
cmd[index][property->type->name] = property->value;
|
||||
cmd[index][std::string{property->type->name}] = property->value;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
@@ -87,8 +87,7 @@ void VirtualServer::executeServerTick() {
|
||||
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
|
||||
return;
|
||||
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
|
||||
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->averagePing();
|
||||
|
||||
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
|
||||
END_TIMINGS(timing_update_states);
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
|
||||
letters = new letter::LetterManager(this);
|
||||
|
||||
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics(), true);
|
||||
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics());
|
||||
|
||||
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(), this->properties()[property::VIRTUALSERVER_NAME].as<string>(), false);
|
||||
static_pointer_cast<InternalClient>(this->serverRoot)->setSharedLock(this->serverRoot);
|
||||
@@ -216,12 +216,13 @@ bool VirtualServer::initialize(bool test_properties) {
|
||||
property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH,
|
||||
property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH,
|
||||
}) {
|
||||
auto info = property::impl::info(type);
|
||||
const auto& info = property::describe(type);
|
||||
auto prop = this->properties()[type];
|
||||
if(prop.default_value() == prop.value()) continue;
|
||||
if(!info->validate_input(this->properties()[type].value())) {
|
||||
this->properties()[type] = info->default_value;
|
||||
logMessage(this->getServerId(), "Server property " + info->name + " contains an invalid value! Resetting it.");
|
||||
|
||||
if(!info.validate_input(this->properties()[type].value())) {
|
||||
this->properties()[type] = info.default_value;
|
||||
logMessage(this->getServerId(), "Server property " + std::string{info.name} + " contains an invalid value! Resetting it.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,8 +712,8 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
|
||||
cmd["invokeruid"] = invoker->getUid();
|
||||
cmd["reasonid"] = ViewReasonId::VREASON_EDITED;
|
||||
for(const auto& key : keys) {
|
||||
auto info = property::impl::info<property::VirtualServerProperties>(key);
|
||||
if(*info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
const auto& info = property::find<property::VirtualServerProperties>(key);
|
||||
if(info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
logError(this->getServerId(), "Tried to broadcast a server update with an unknown info: " + key);
|
||||
continue;
|
||||
}
|
||||
@@ -724,7 +725,7 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<shared_ptr<property::PropertyDescription>>& keys, bool selfNotify) {
|
||||
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<const property::PropertyDescription*>& keys, bool selfNotify) {
|
||||
if(keys.empty()) return false;
|
||||
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
|
||||
shared_lock client_channel_lock(client->channel_lock);
|
||||
@@ -1017,35 +1018,33 @@ bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
|
||||
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].as<std::string>();
|
||||
}
|
||||
|
||||
float VirtualServer::averagePacketLoss() {
|
||||
//TODO Average packet loss
|
||||
return 0.f;
|
||||
}
|
||||
VirtualServer::NetworkReport VirtualServer::generate_network_report() {
|
||||
double total_ping{0}, total_loss{0};
|
||||
size_t pings_counted{0}, loss_counted{0};
|
||||
|
||||
float VirtualServer::averagePing() {
|
||||
float count = 0;
|
||||
float sum = 0;
|
||||
|
||||
this->forEachClient([&count, &sum](shared_ptr<ConnectedClient> client) {
|
||||
auto type = client->getType();
|
||||
if(type == ClientType::CLIENT_TEAMSPEAK || type == ClientType::CLIENT_TEASPEAK) {
|
||||
count++;
|
||||
sum += duration_cast<milliseconds>(dynamic_pointer_cast<VoiceClient>(client)->calculatePing()).count();
|
||||
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
|
||||
if(auto vc = dynamic_pointer_cast<VoiceClient>(client); vc) {
|
||||
total_ping += vc->current_ping().count();
|
||||
total_loss += vc->current_packet_loss();
|
||||
pings_counted++;
|
||||
loss_counted++;
|
||||
}
|
||||
#ifdef COMPILE_WEB_CLIENT
|
||||
else if(type == ClientType::CLIENT_WEB) {
|
||||
count++;
|
||||
sum += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
|
||||
else if(client->getType() == ClientType::CLIENT_WEB) {
|
||||
pings_counted++;
|
||||
total_ping += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
if(count == 0) return 0;
|
||||
return sum / count;
|
||||
VirtualServer::NetworkReport result{};
|
||||
if(loss_counted) result.average_loss = total_loss / loss_counted;
|
||||
if(pings_counted) result.average_ping = total_ping / pings_counted;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool VirtualServer::resetPermissions(std::string& token) {
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
|
||||
|
||||
|
||||
@@ -136,6 +136,11 @@ namespace ts {
|
||||
friend class InstanceHandler;
|
||||
friend class VirtualServerManager;
|
||||
public:
|
||||
struct NetworkReport {
|
||||
float average_ping{0};
|
||||
float average_loss{0};
|
||||
};
|
||||
|
||||
VirtualServer(ServerId serverId, sql::SqlManager*);
|
||||
~VirtualServer();
|
||||
|
||||
@@ -182,11 +187,11 @@ namespace ts {
|
||||
inline GroupManager* getGroupManager() { return this->groups; }
|
||||
|
||||
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
|
||||
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<std::shared_ptr<property::PropertyDescription>>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
|
||||
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<const property::PropertyDescription*>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
|
||||
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
|
||||
if(keys.empty()) return false;
|
||||
std::deque<std::shared_ptr<property::PropertyDescription>> _keys;
|
||||
for(const auto& key : keys) _keys.push_back(property::impl::info<property::ClientProperties>(key));
|
||||
std::deque<const property::PropertyDescription*> _keys{};
|
||||
for(const auto& key : keys) _keys.push_back(&property::describe(key));
|
||||
return this->notifyClientPropertyUpdates(client, _keys, selfNotify);
|
||||
};
|
||||
|
||||
@@ -230,8 +235,7 @@ namespace ts {
|
||||
|
||||
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);
|
||||
|
||||
float averagePing();
|
||||
float averagePacketLoss();
|
||||
[[nodiscard]] NetworkReport generate_network_report();
|
||||
|
||||
bool resetPermissions(std::string&);
|
||||
void ensureValidDefaultGroups();
|
||||
|
||||
@@ -13,7 +13,7 @@ using namespace std::chrono;
|
||||
using namespace ts::server;
|
||||
|
||||
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
|
||||
this->puzzles = new protocol::PuzzleManager();
|
||||
this->puzzles = new udp::PuzzleManager{};
|
||||
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
|
||||
this->execute_loop = new event::EventExecutor("executor #");
|
||||
//this->join_loop = new event::EventExecutor("joiner #");
|
||||
@@ -67,7 +67,8 @@ bool VirtualServerManager::initialize(bool autostart) {
|
||||
this->state = State::STARTING;
|
||||
logMessage(LOG_INSTANCE, "Generating server puzzles...");
|
||||
auto start = system_clock::now();
|
||||
this->puzzles->precomputePuzzles(config::voice::DefaultPuzzlePrecomputeSize);
|
||||
if(!this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize))
|
||||
logCritical(LOG_INSTANCE, "Failed to precompute RSA puzzles");
|
||||
logMessage(LOG_INSTANCE, "Puzzles generated! Time required: " + to_string(duration_cast<milliseconds>(system_clock::now() - start).count()) + "ms");
|
||||
|
||||
size_t serverCount = 0;
|
||||
@@ -317,7 +318,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
||||
return nullptr;
|
||||
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
//`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT
|
||||
|
||||
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
|
||||
variable{":target_sid", serverId},
|
||||
variable{":type", property::PROP_TYPE_SERVER}).execute();
|
||||
@@ -393,21 +394,8 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
||||
}
|
||||
|
||||
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `tokens` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `properties` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `permissions` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `clients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `channels` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `servers` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
sql::command(this->handle->getSql(), "DELETE FROM `ban_trigger` WHERE `server_id` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
this->delete_server_in_db(server->serverId);
|
||||
this->handle->getFileServer()->deleteServer(server);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -446,4 +434,32 @@ void VirtualServerManager::tickHandshakeClients() {
|
||||
if(vserver)
|
||||
vserver->tickHandshakingClients();
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id) {
|
||||
#define execute_delete(statement) \
|
||||
result = sql::command(this->handle->getSql(), statement, variable{":sid", server_id}).execute(); \
|
||||
if(!result) { \
|
||||
logWarning(LOG_INSTANCE, "Failed to execute SQL command {}: {}", statement, result.fmtStr()); \
|
||||
result = sql::result{}; \
|
||||
}
|
||||
|
||||
sql::result result{};
|
||||
|
||||
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `clients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
|
||||
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
|
||||
|
||||
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
|
||||
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
|
||||
}
|
||||
@@ -5,95 +5,104 @@
|
||||
#include "client/voice/PrecomputedPuzzles.h"
|
||||
#include "server/VoiceIOManager.h"
|
||||
#include "VirtualServer.h"
|
||||
#include <query/command3.h>
|
||||
#include "snapshots/snapshot.h"
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class InstanceHandler;
|
||||
namespace ts::server {
|
||||
class InstanceHandler;
|
||||
|
||||
struct ServerReport {
|
||||
size_t avariable;
|
||||
size_t online;
|
||||
struct ServerReport {
|
||||
size_t avariable;
|
||||
size_t online;
|
||||
|
||||
size_t slots;
|
||||
size_t onlineClients;
|
||||
size_t onlineChannels;
|
||||
};
|
||||
class VirtualServerManager {
|
||||
public:
|
||||
enum State {
|
||||
STOPPED,
|
||||
STARTING,
|
||||
STARTED,
|
||||
STOPPING
|
||||
};
|
||||
size_t slots;
|
||||
size_t onlineClients;
|
||||
size_t onlineChannels;
|
||||
};
|
||||
class VirtualServerManager {
|
||||
public:
|
||||
enum State {
|
||||
STOPPED,
|
||||
STARTING,
|
||||
STARTED,
|
||||
STOPPING
|
||||
};
|
||||
|
||||
explicit VirtualServerManager(InstanceHandler*);
|
||||
~VirtualServerManager();
|
||||
explicit VirtualServerManager(InstanceHandler*);
|
||||
~VirtualServerManager();
|
||||
|
||||
bool initialize(bool execute_autostart = true);
|
||||
bool initialize(bool execute_autostart = true);
|
||||
|
||||
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
|
||||
bool deleteServer(std::shared_ptr<VirtualServer>);
|
||||
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
|
||||
bool deleteServer(std::shared_ptr<VirtualServer>);
|
||||
|
||||
std::shared_ptr<VirtualServer> findServerById(ServerId);
|
||||
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
|
||||
uint16_t next_available_port();
|
||||
ServerId next_available_server_id(bool& /* success */);
|
||||
|
||||
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
|
||||
threads::MutexLock l(this->instanceLock);
|
||||
return instances;
|
||||
}
|
||||
std::shared_ptr<VirtualServer> findServerById(ServerId);
|
||||
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
|
||||
uint16_t next_available_port();
|
||||
ServerId next_available_server_id(bool& /* success */);
|
||||
|
||||
ServerReport report();
|
||||
OnlineClientReport clientReport();
|
||||
size_t runningServers();
|
||||
size_t usedSlots();
|
||||
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
|
||||
threads::MutexLock l(this->instanceLock);
|
||||
return instances;
|
||||
}
|
||||
|
||||
void executeAutostart();
|
||||
void shutdownAll(const std::string&);
|
||||
ServerReport report();
|
||||
OnlineClientReport clientReport();
|
||||
size_t runningServers();
|
||||
size_t usedSlots();
|
||||
|
||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
||||
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
||||
void executeAutostart();
|
||||
void shutdownAll(const std::string&);
|
||||
|
||||
protocol::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
||||
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
||||
bool deploy_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
|
||||
event::EventExecutor* get_join_loop() { return this->join_loop; }
|
||||
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
|
||||
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||
|
||||
inline void adjust_executor_threads() {
|
||||
std::unique_lock instance_lock(this->instanceLock);
|
||||
auto instance_count = this->instances.size();
|
||||
instance_lock.unlock();
|
||||
event::EventExecutor* get_join_loop() { return this->join_loop; }
|
||||
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
|
||||
|
||||
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
|
||||
this->execute_loop->threads(threads);
|
||||
}
|
||||
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
||||
inline void adjust_executor_threads() {
|
||||
std::unique_lock instance_lock(this->instanceLock);
|
||||
auto instance_count = this->instances.size();
|
||||
instance_lock.unlock();
|
||||
|
||||
threads::Mutex server_create_lock;
|
||||
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
|
||||
this->execute_loop->threads(threads);
|
||||
}
|
||||
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
|
||||
|
||||
State getState() { return this->state; }
|
||||
private:
|
||||
State state = State::STOPPED;
|
||||
InstanceHandler* handle;
|
||||
threads::Mutex instanceLock;
|
||||
std::deque<std::shared_ptr<VirtualServer>> instances;
|
||||
protocol::PuzzleManager* puzzles = nullptr;
|
||||
threads::Mutex server_create_lock;
|
||||
|
||||
event::EventExecutor* execute_loop = nullptr;
|
||||
event::EventExecutor* join_loop = nullptr;
|
||||
threads::Scheduler* handshakeTickers = nullptr;
|
||||
io::VoiceIOManager* _ioManager = nullptr;
|
||||
State getState() { return this->state; }
|
||||
private:
|
||||
State state = State::STOPPED;
|
||||
InstanceHandler* handle;
|
||||
threads::Mutex instanceLock;
|
||||
std::deque<std::shared_ptr<VirtualServer>> instances;
|
||||
udp::PuzzleManager* puzzles{nullptr};
|
||||
|
||||
struct {
|
||||
std::thread executor{};
|
||||
std::condition_variable condition;
|
||||
std::mutex lock;
|
||||
} acknowledge;
|
||||
event::EventExecutor* execute_loop = nullptr;
|
||||
event::EventExecutor* join_loop = nullptr;
|
||||
threads::Scheduler* handshakeTickers = nullptr;
|
||||
io::VoiceIOManager* _ioManager = nullptr;
|
||||
|
||||
void tickHandshakeClients();
|
||||
};
|
||||
}
|
||||
struct {
|
||||
std::thread executor{};
|
||||
std::condition_variable condition;
|
||||
std::mutex lock;
|
||||
} acknowledge;
|
||||
|
||||
void tickHandshakeClients();
|
||||
|
||||
void delete_server_in_db(ServerId /* server id */);
|
||||
|
||||
/* methods used to preprocess a snapshot */
|
||||
bool deploy_ts3_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
bool deploy_teaspeak_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
/* actual deploy method */
|
||||
bool deploy_raw_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
||||
};
|
||||
}
|
||||
@@ -29,9 +29,7 @@ ConnectedClient::ConnectedClient(sql::SqlManager* db, const std::shared_ptr<Virt
|
||||
memtrack::allocated<ConnectedClient>(this);
|
||||
memset(&this->remote_address, 0, sizeof(this->remote_address));
|
||||
|
||||
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr, false);
|
||||
this->connectionStatistics->measure_bandwidths(false); /* done by the client and we trust this */
|
||||
|
||||
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr);
|
||||
channels = make_shared<ClientChannelView>(this);
|
||||
}
|
||||
|
||||
@@ -381,7 +379,7 @@ bool ConnectedClient::notifyClientLeftView(
|
||||
std::shared_ptr<ConnectedClient> invoker,
|
||||
bool lock_channel_tree) {
|
||||
assert(!lock_channel_tree); /* not supported yet! */
|
||||
assert(client && client->getClientId() != 0);
|
||||
assert(client == this || (client && client->getClientId() != 0));
|
||||
assert(client->currentChannel || &*client == this);
|
||||
|
||||
if(client != this) {
|
||||
@@ -744,17 +742,14 @@ void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
}
|
||||
|
||||
|
||||
if(this->last_statistics_tick + seconds(5) < time) {
|
||||
this->last_statistics_tick = time;
|
||||
this->connectionStatistics->tick();
|
||||
}
|
||||
this->connectionStatistics->tick();
|
||||
}
|
||||
|
||||
void ConnectedClient::sendServerInit() {
|
||||
Command command("initserver");
|
||||
|
||||
for(const auto& prop : this->server->properties().list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
|
||||
command[prop.type().name] = prop.value();
|
||||
command[std::string{prop.type().name}] = prop.value();
|
||||
}
|
||||
command["virtualserver_maxclients"] = 32;
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace ts {
|
||||
virtual bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg);
|
||||
virtual bool notifyClientUpdated(
|
||||
const std::shared_ptr<ConnectedClient> &,
|
||||
const std::deque<std::shared_ptr<property::PropertyDescription>> &,
|
||||
const std::deque<const property::PropertyDescription*> &,
|
||||
bool lock_channel_tree
|
||||
); /* invalid client id causes error: invalid clientID */
|
||||
|
||||
@@ -326,7 +326,6 @@ namespace ts {
|
||||
std::chrono::system_clock::time_point lastOnlineTimestamp;
|
||||
std::chrono::system_clock::time_point lastTransfareTimestamp;
|
||||
std::chrono::system_clock::time_point idleTimestamp;
|
||||
std::chrono::system_clock::time_point last_statistics_tick;
|
||||
|
||||
struct {
|
||||
std::mutex lock;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,7 @@ bool ConnectedClient::notifyServerGroupList() {
|
||||
cmd[index]["sgid"] = group->groupId();
|
||||
}
|
||||
for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
|
||||
cmd[index][prop.type().name] = prop.value();
|
||||
cmd[index][std::string{prop.type().name}] = prop.value();
|
||||
|
||||
|
||||
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
|
||||
@@ -186,7 +186,7 @@ bool ConnectedClient::notifyChannelGroupList() {
|
||||
cmd[index]["sgid"] = group->groupId();
|
||||
}
|
||||
for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
|
||||
cmd[index][prop.type().name] = prop.value();
|
||||
cmd[index][std::string{prop.type().name}] = prop.value();
|
||||
|
||||
|
||||
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
|
||||
@@ -271,108 +271,116 @@ bool ConnectedClient::notifyClientChannelGroupChanged(
|
||||
}
|
||||
|
||||
bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &target, const shared_ptr<ConnectionInfoData> &info) {
|
||||
Command notify("notifyconnectioninfo");
|
||||
notify["clid"] = target->getClientId();
|
||||
command_builder notify{"notifyconnectioninfo"};
|
||||
auto bulk = notify.bulk(0);
|
||||
bulk.put_unchecked("clid", target->getClientId());
|
||||
|
||||
auto not_set = this->getType() == CLIENT_TEAMSPEAK ? 0 : -1;
|
||||
/* we deliver data to the web client as well, because its a bit dump :D */
|
||||
if(target->getClientId() != this->getClientId()) {
|
||||
auto report = target->connectionStatistics->full_report();
|
||||
auto file_stats = target->connectionStatistics->file_stats();
|
||||
|
||||
/* default values which normally sets the client */
|
||||
notify["connection_bandwidth_received_last_minute_control"] = not_set;
|
||||
notify["connection_bandwidth_received_last_minute_keepalive"] = not_set;
|
||||
notify["connection_bandwidth_received_last_minute_speech"] = not_set;
|
||||
notify["connection_bandwidth_received_last_second_control"] = not_set;
|
||||
notify["connection_bandwidth_received_last_second_keepalive"] = not_set;
|
||||
notify["connection_bandwidth_received_last_second_speech"] = not_set;
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, not_set);
|
||||
|
||||
notify["connection_bandwidth_sent_last_minute_control"] = not_set;
|
||||
notify["connection_bandwidth_sent_last_minute_keepalive"] = not_set;
|
||||
notify["connection_bandwidth_sent_last_minute_speech"] = not_set;
|
||||
notify["connection_bandwidth_sent_last_second_control"] = not_set;
|
||||
notify["connection_bandwidth_sent_last_second_keepalive"] = not_set;
|
||||
notify["connection_bandwidth_sent_last_second_speech"] = not_set;
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, not_set);
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
notify["connection_bytes_received_control"] = not_set;
|
||||
notify["connection_bytes_received_keepalive"] = not_set;
|
||||
notify["connection_bytes_received_speech"] = not_set;
|
||||
notify["connection_bytes_sent_control"] = not_set;
|
||||
notify["connection_bytes_sent_keepalive"] = not_set;
|
||||
notify["connection_bytes_sent_speech"] = not_set;
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_SPEECH, not_set);
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
notify["connection_packets_received_control"] = not_set;
|
||||
notify["connection_packets_received_keepalive"] = not_set;
|
||||
notify["connection_packets_received_speech"] = not_set;
|
||||
notify["connection_packets_sent_control"] = not_set;
|
||||
notify["connection_packets_sent_keepalive"] = not_set;
|
||||
notify["connection_packets_sent_speech"] = not_set;
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_SPEECH, not_set);
|
||||
|
||||
notify["connection_server2client_packetloss_control"] = not_set;
|
||||
notify["connection_server2client_packetloss_keepalive"] = not_set;
|
||||
notify["connection_server2client_packetloss_speech"] = not_set;
|
||||
notify["connection_server2client_packetloss_total"] = not_set;
|
||||
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, not_set);
|
||||
|
||||
notify["connection_ping"] = 0;
|
||||
notify["connection_ping_deviation"] = 0;
|
||||
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, not_set);
|
||||
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, not_set);
|
||||
|
||||
notify["connection_connected_time"] = 0;
|
||||
notify["connection_idle_time"] = 0;
|
||||
bulk.put_unchecked(property::CONNECTION_PING, 0);
|
||||
bulk.put_unchecked(property::CONNECTION_PING_DEVIATION, 0);
|
||||
|
||||
bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, 0);
|
||||
bulk.put_unchecked(property::CONNECTION_IDLE_TIME, 0);
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
notify["connection_filetransfer_bandwidth_sent"] = report.file_bytes_received;
|
||||
notify["connection_filetransfer_bandwidth_received"] = report.file_bytes_sent;
|
||||
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, file_stats.bytes_received);
|
||||
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, file_stats.bytes_sent);
|
||||
}
|
||||
|
||||
if(info) {
|
||||
for(const auto& elm : info->properties) {
|
||||
notify[elm.first] = elm.second;
|
||||
}
|
||||
for(const auto& [key, value] : info->properties)
|
||||
bulk.put(key, value);
|
||||
} else {
|
||||
//Fill in some server stuff
|
||||
if(dynamic_pointer_cast<VoiceClient>(target))
|
||||
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<VoiceClient>(target)->calculatePing()).count();
|
||||
//Fill in what we can, else we trust the client
|
||||
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
|
||||
auto& stats = target->connectionStatistics->total_stats();
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
bulk.put(property::CONNECTION_BYTES_RECEIVED_CONTROL, stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND]);
|
||||
bulk.put(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
|
||||
bulk.put(property::CONNECTION_BYTES_RECEIVED_SPEECH, stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE]);
|
||||
bulk.put(property::CONNECTION_BYTES_SENT_CONTROL, stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND]);
|
||||
bulk.put(property::CONNECTION_BYTES_SENT_KEEPALIVE, stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
|
||||
bulk.put(property::CONNECTION_BYTES_SENT_SPEECH, stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE]);
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
bulk.put(property::CONNECTION_PACKETS_RECEIVED_CONTROL, stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND]);
|
||||
bulk.put(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
|
||||
bulk.put(property::CONNECTION_PACKETS_RECEIVED_SPEECH, stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE]);
|
||||
bulk.put(property::CONNECTION_PACKETS_SENT_CONTROL, stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND]);
|
||||
bulk.put(property::CONNECTION_PACKETS_SENT_KEEPALIVE, stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
|
||||
bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]);
|
||||
}
|
||||
}
|
||||
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc) {
|
||||
bulk.put(property::CONNECTION_PING, floor<milliseconds>(vc->current_ping()).count());
|
||||
bulk.put(property::CONNECTION_PING_DEVIATION, vc->current_ping_deviation());
|
||||
}
|
||||
#ifdef COMPILE_WEB_CLIENT
|
||||
else if(dynamic_pointer_cast<WebClient>(target))
|
||||
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count();
|
||||
else if(dynamic_pointer_cast<WebClient>(target))
|
||||
bulk.put(property::CONNECTION_PING, floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count());
|
||||
#endif
|
||||
|
||||
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
|
||||
auto report = target->connectionStatistics->full_report();
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
notify["connection_bytes_received_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
notify["connection_bytes_received_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
|
||||
notify["connection_bytes_received_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
notify["connection_bytes_sent_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
notify["connection_bytes_sent_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
|
||||
notify["connection_bytes_sent_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
|
||||
/* its flipped here because the report is out of the clients view */
|
||||
notify["connection_packets_received_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
notify["connection_packets_received_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
|
||||
notify["connection_packets_received_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
notify["connection_packets_sent_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
notify["connection_packets_sent_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
|
||||
notify["connection_packets_sent_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
}
|
||||
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc){
|
||||
auto& calculator = vc->connection->packet_statistics();
|
||||
auto report = calculator.loss_report();
|
||||
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, std::to_string(report.voice_loss()));
|
||||
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, std::to_string(report.keep_alive_loss()));
|
||||
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, std::to_string(report.control_loss()));
|
||||
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, std::to_string(report.total_loss()));
|
||||
}
|
||||
|
||||
if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) {
|
||||
notify["connection_client_ip"] = target->getLoggingPeerIp();
|
||||
notify["connection_client_port"] = target->getPeerPort();
|
||||
bulk.put(property::CONNECTION_CLIENT_IP, target->getLoggingPeerIp());
|
||||
bulk.put(property::CONNECTION_CLIENT_PORT, target->getPeerPort());
|
||||
}
|
||||
|
||||
//Needs to be filled out
|
||||
notify["connection_client2server_packetloss_speech"] = not_set;
|
||||
notify["connection_client2server_packetloss_keepalive"] = not_set;
|
||||
notify["connection_client2server_packetloss_control"] = not_set;
|
||||
notify["connection_client2server_packetloss_total"] = not_set;
|
||||
|
||||
notify["connection_connected_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count();
|
||||
notify["connection_idle_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count();
|
||||
bulk.put(property::CONNECTION_CONNECTED_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count());
|
||||
bulk.put(property::CONNECTION_IDLE_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count());
|
||||
this->sendCommand(notify);
|
||||
return true;
|
||||
}
|
||||
@@ -404,7 +412,7 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<shared_ptr<property::PropertyDescription>> &props, bool lock) {
|
||||
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<const property::PropertyDescription*> &props, bool lock) {
|
||||
shared_lock channel_lock(this->channel_lock, defer_lock);
|
||||
if(lock)
|
||||
channel_lock.lock();
|
||||
@@ -420,7 +428,7 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient>
|
||||
Command response("notifyclientupdated");
|
||||
response["clid"] = client_id;
|
||||
for (const auto &prop : props) {
|
||||
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (prop->property_index == property::CLIENT_TOTAL_ONLINE_TIME || prop->property_index == property::CLIENT_MONTH_ONLINE_TIME))
|
||||
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (*prop == property::CLIENT_TOTAL_ONLINE_TIME || *prop == property::CLIENT_MONTH_ONLINE_TIME))
|
||||
response[prop->name] = client->properties()[prop].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
|
||||
else
|
||||
response[prop->name] = client->properties()[prop].value();
|
||||
@@ -650,7 +658,7 @@ bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<Con
|
||||
|
||||
this->visibleClients.push_back(client);
|
||||
for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
|
||||
cmd[index][elm.type().name] = elm.value();
|
||||
cmd[index][std::string{elm.type().name}] = elm.value();
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -678,14 +686,14 @@ bool ConnectedClient::notifyChannelEdited(
|
||||
|
||||
Command notify("notifychanneledited");
|
||||
for(auto prop : properties) {
|
||||
const auto& prop_info = property::impl::info(prop);
|
||||
const auto& prop_info = property::describe(prop);
|
||||
|
||||
if(prop == property::CHANNEL_ORDER)
|
||||
notify[prop_info->name] = v_channel->previous_channel;
|
||||
notify[prop_info.name] = v_channel->previous_channel;
|
||||
else if(prop == property::CHANNEL_DESCRIPTION) {
|
||||
send_description_change = true;
|
||||
} else {
|
||||
notify[prop_info->name] = channel->properties()[prop].as<string>();
|
||||
notify[prop_info.name] = channel->properties()[prop].as<string>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ bool ConnectedClient::handle_text_command(
|
||||
for(const auto& property : bot->properties()->list_properties(~0)) {
|
||||
if(find(editable_properties.begin(), editable_properties.end(), property.type().name) == editable_properties.end()) continue;
|
||||
|
||||
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
|
||||
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
|
||||
}
|
||||
} else if(arguments.size() < 4) {
|
||||
if(find(editable_properties.begin(), editable_properties.end(), arguments[2]) == editable_properties.end()) {
|
||||
@@ -494,14 +494,14 @@ bool ConnectedClient::handle_text_command(
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::ClientProperties>(arguments[2]);
|
||||
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::CLIENT_UNDEFINED) {
|
||||
const auto &property_info = property::find<property::ClientProperties>(arguments[2]);
|
||||
if(property_info.is_undefined()) {
|
||||
send_message(bot, "Unknown property " + arguments[2] + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto prop = bot->properties()[(property::ClientProperties) property_info->property_index];
|
||||
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
|
||||
auto prop = bot->properties()[(property::ClientProperties) property_info.property_index];
|
||||
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
|
||||
return true;
|
||||
} else {
|
||||
Command cmd("");
|
||||
@@ -527,36 +527,36 @@ bool ConnectedClient::handle_text_command(
|
||||
if(arguments.size() < 3) {
|
||||
send_message(bot, "Playlist properties:");
|
||||
for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) {
|
||||
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
|
||||
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
|
||||
}
|
||||
} else if(arguments.size() < 4) {
|
||||
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
|
||||
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
|
||||
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
|
||||
if(property_info.is_undefined()) {
|
||||
send_message(bot, "Unknown property " + arguments[2] + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto prop = playlist->properties()[(property::PlaylistProperties) property_info->property_index];
|
||||
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
|
||||
auto prop = playlist->properties()[(property::PlaylistProperties) property_info.property_index];
|
||||
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
|
||||
} else {
|
||||
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
|
||||
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
|
||||
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
|
||||
if(property_info.is_undefined()) {
|
||||
send_message(bot, "Unknown property " + arguments[2] + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
JOIN_ARGS(value, 3);
|
||||
if(!property_info->validate_input(value)) {
|
||||
if(!property_info.validate_input(value)) {
|
||||
send_message(bot, "Please enter a valid value!");
|
||||
return true;
|
||||
}
|
||||
|
||||
if((property_info->flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
if((property_info.flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
send_message(bot, "This property isnt changeable!");
|
||||
return true;
|
||||
}
|
||||
|
||||
playlist->properties()[(property::PlaylistProperties) property_info->property_index] = value;
|
||||
playlist->properties()[(property::PlaylistProperties) property_info.property_index] = value;
|
||||
send_message(bot, "Property successfully changed");
|
||||
return true;
|
||||
}
|
||||
@@ -641,9 +641,42 @@ bool ConnectedClient::handle_text_command(
|
||||
|
||||
auto id = vc->getConnection()->getPacketIdManager().currentPacketId(type);
|
||||
auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
|
||||
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
|
||||
|
||||
send_message(_this.lock(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
|
||||
//auto& buffer = vc->getConnection()->packet_buffers()[type.type()];
|
||||
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(buffer.generation(0)) + " id: " + to_string(buffer.current_index()));
|
||||
send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "ping")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
auto& ack = vc->connection->getAcknowledgeManager();
|
||||
send_message(_this.lock(), "Command retransmission values:");
|
||||
send_message(_this.lock(), " RTO : " + std::to_string(ack.current_rto()));
|
||||
send_message(_this.lock(), " RTTVAR: " + std::to_string(ack.current_rttvar()));
|
||||
send_message(_this.lock(), " SRTT : " + std::to_string(ack.current_srtt()));
|
||||
return true;
|
||||
} else if(TARG(0, "sgeneration")) {
|
||||
TLEN(4);
|
||||
|
||||
try {
|
||||
auto type = stol(arguments[1]);
|
||||
auto generation = stol(arguments[2]);
|
||||
auto pid = stol(arguments[3]);
|
||||
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
|
||||
if(type >= genestis.size()) {
|
||||
send_message(_this.lock(), "Invalid type");
|
||||
return true;
|
||||
}
|
||||
genestis[type].set_last_state(pid, generation);
|
||||
} catch(std::exception& ex) {
|
||||
send_message(_this.lock(), "Failed to parse argument");
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "disconnect")) {
|
||||
|
||||
@@ -45,10 +45,17 @@ namespace ts {
|
||||
return std::forward<F>(f)(*handle);
|
||||
}
|
||||
|
||||
/*
|
||||
template <typename T>
|
||||
ts::PropertyWrapper operator[](T type) {
|
||||
return (*handle)[type];
|
||||
}
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
ts::PropertyWrapper operator[](const T& type) {
|
||||
return (*handle)[type];
|
||||
}
|
||||
};
|
||||
|
||||
DataClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>&);
|
||||
|
||||
@@ -65,7 +65,6 @@ void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head
|
||||
if(!speaking_client) return true;
|
||||
|
||||
return !speaking_client->shouldReceiveVoice(self);
|
||||
|
||||
}), target_clients.end());
|
||||
if(target_clients.empty()) {
|
||||
return;
|
||||
@@ -480,13 +479,13 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto &info = property::info<property::ClientProperties>(key);
|
||||
if(*info == property::CLIENT_UNDEFINED) {
|
||||
const auto &info = property::find<property::ClientProperties>(key);
|
||||
if(info.is_undefined()) {
|
||||
logError(this->getServerId(), "{} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known!", CLIENT_STR_LOG_PREFIX, key);
|
||||
continue;
|
||||
//return {findError("parameter_invalid"), "Unknown property " + key};
|
||||
}
|
||||
if(!info->validate_input(cmd[key].as<string>()))
|
||||
if(!info.validate_input(cmd[key].as<string>()))
|
||||
return command_result{error::parameter_invalid};
|
||||
|
||||
this->properties()[info] = cmd[key].as<std::string>();
|
||||
@@ -743,7 +742,7 @@ void SpeakingClient::processJoin() {
|
||||
|
||||
unique_lock server_channel_lock(this->server->channel_tree_lock);
|
||||
this->server->client_move(this->ref(), channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
|
||||
this->subscribeChannel({this->currentChannel}, false, true);
|
||||
if(this->getType() != ClientType::CLIENT_TEAMSPEAK) this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */
|
||||
}
|
||||
TIMING_STEP(timings, "join move ");
|
||||
|
||||
@@ -761,6 +760,27 @@ void SpeakingClient::processJoin() {
|
||||
this->connectTimestamp = chrono::system_clock::now();
|
||||
this->idleTimestamp = chrono::system_clock::now();
|
||||
|
||||
TIMING_STEP(timings, "welcome msg");
|
||||
{
|
||||
std::string message{};
|
||||
config::server::clients::WelcomeMessageType type{config::server::clients::WELCOME_MESSAGE_TYPE_NONE};
|
||||
if(this->getType() == ClientType::CLIENT_TEASPEAK) {
|
||||
message = config::server::clients::extra_welcome_message_teaspeak;
|
||||
type = config::server::clients::extra_welcome_message_type_teaspeak;
|
||||
} else if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
message = config::server::clients::extra_welcome_message_teamspeak;
|
||||
type = config::server::clients::extra_welcome_message_type_teamspeak;
|
||||
} else if(this->getType() == ClientType::CLIENT_WEB) {
|
||||
message = config::server::clients::extra_welcome_message_teaweb;
|
||||
type = config::server::clients::extra_welcome_message_type_teaweb;
|
||||
}
|
||||
|
||||
if(type == config::server::clients::WELCOME_MESSAGE_TYPE_POKE) {
|
||||
this->notifyClientPoke(this->server->serverRoot, message);
|
||||
} else if(type == config::server::clients::WELCOME_MESSAGE_TYPE_CHAT) {
|
||||
this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, this->server->serverRoot, 0, 0, std::chrono::system_clock::now(), message);
|
||||
}
|
||||
}
|
||||
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
|
||||
}
|
||||
|
||||
|
||||
@@ -529,51 +529,66 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||
CMD_CHK_PARM_COUNT(1);
|
||||
|
||||
//TODO: Use for this here the cache as well!
|
||||
auto permission_cache = make_shared<CalculateCache>();
|
||||
if (cmd[0].has("cpid") && cmd["cpid"].as<uint64_t>() != 0) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_child, 1, permission_cache);
|
||||
if (cmd[0].has("channel_order")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_sortorder, 1, permission_cache);
|
||||
|
||||
if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false;
|
||||
if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false;
|
||||
if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false;
|
||||
if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false;
|
||||
|
||||
if (cmd[0]["channel_flag_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_permanent, 1, permission_cache);
|
||||
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_semi_permanent, 1, permission_cache);
|
||||
else ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_temporary, 1, permission_cache);
|
||||
|
||||
if (!cmd[0]["channel_flag_permanent"].as<bool>() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"};
|
||||
|
||||
if (cmd[0]["channel_flag_default"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_default, 1, permission_cache);
|
||||
if (cmd[0]["channel_flag_password"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_password, 1, permission_cache);
|
||||
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, 0, false, permission_cache)))
|
||||
return command_result{permission::b_channel_create_modify_with_force_password};
|
||||
|
||||
if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY)
|
||||
cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string()));
|
||||
if (cmd[0].has("channel_description")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_description, 1, permission_cache);
|
||||
if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as<bool>())) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxclients, 1, permission_cache);
|
||||
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
|
||||
cmd["channel_maxclients"] = -1;
|
||||
cmd["channel_flag_maxclients_unlimited"] = 1;
|
||||
}
|
||||
}
|
||||
if (cmd[0].has("channel_maxfamilyclients")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxfamilyclients, 1, permission_cache);
|
||||
if (cmd[0].has("channel_needed_talk_power")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_needed_talk_power, 1, permission_cache);
|
||||
if (cmd[0].has("channel_topic")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_topic, 1, permission_cache);
|
||||
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
|
||||
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
|
||||
|
||||
auto target_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();
|
||||
auto& tree_lock = this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock();
|
||||
unique_lock tree_channel_lock(tree_lock);
|
||||
|
||||
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
|
||||
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
|
||||
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
|
||||
}
|
||||
ChannelId parent_channel_id = parent ? parent->entry->channelId() : 0;
|
||||
|
||||
#define test_permission(required, permission_type) \
|
||||
do {\
|
||||
if(!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false, permission_cache))) \
|
||||
return command_result{permission_type};\
|
||||
} while(0)
|
||||
|
||||
|
||||
//TODO: Use for this here the cache as well!
|
||||
auto permission_cache = make_shared<CalculateCache>();
|
||||
if(parent) test_permission(1, permission::b_channel_create_child);
|
||||
if (cmd[0].has("channel_order")) test_permission(1, permission::b_channel_create_with_sortorder);
|
||||
if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false;
|
||||
if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false;
|
||||
if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false;
|
||||
if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false;
|
||||
|
||||
if (cmd[0]["channel_flag_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_permanent);
|
||||
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_semi_permanent);
|
||||
else test_permission(1, permission::b_channel_create_temporary);
|
||||
|
||||
if (!cmd[0]["channel_flag_permanent"].as<bool>() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"};
|
||||
|
||||
if (cmd[0]["channel_flag_default"].as<bool>()) test_permission(1, permission::b_channel_create_with_default);
|
||||
if (cmd[0]["channel_flag_password"].as<bool>()) test_permission(1, permission::b_channel_create_with_password);
|
||||
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, parent_channel_id, false, permission_cache)))
|
||||
return command_result{permission::b_channel_create_modify_with_force_password};
|
||||
|
||||
if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY)
|
||||
cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string()));
|
||||
if (cmd[0].has("channel_description")) test_permission(1, permission::b_channel_create_with_description);
|
||||
if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as<bool>())) {
|
||||
test_permission(1, permission::b_channel_create_with_maxclients);
|
||||
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
|
||||
cmd["channel_maxclients"] = -1;
|
||||
cmd["channel_flag_maxclients_unlimited"] = 1;
|
||||
}
|
||||
}
|
||||
if (cmd[0].has("channel_maxfamilyclients")) test_permission(1, permission::b_channel_create_with_maxfamilyclients);
|
||||
if (cmd[0].has("channel_needed_talk_power")) test_permission(1,permission::b_channel_create_with_needed_talk_power);
|
||||
if (cmd[0].has("channel_topic")) test_permission(1,permission::b_channel_create_with_topic);
|
||||
|
||||
if(cmd[0].has("channel_conversation_history_length")) {
|
||||
auto value = cmd["channel_conversation_history_length"].as<int64_t>();
|
||||
if(value == 0) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_modify_conversation_history_unlimited, 1, permission_cache);
|
||||
test_permission(1, permission::b_channel_create_modify_conversation_history_unlimited);
|
||||
} else {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_conversation_history_length, 1, permission_cache);
|
||||
test_permission(1, permission::i_channel_create_modify_conversation_history_length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,9 +600,10 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
else
|
||||
cmd["channel_delete_delay"] = 0;
|
||||
} else {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission_cache);
|
||||
test_permission(cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission::i_channel_create_modify_with_temp_delete_delay);
|
||||
}
|
||||
}
|
||||
#undef test_permission
|
||||
|
||||
{
|
||||
size_t created_total = 0, created_tmp = 0, created_semi = 0, created_perm = 0;
|
||||
@@ -607,14 +623,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
if(this->server && created_total >= this->server->properties()[property::VIRTUALSERVER_MAX_CHANNELS].as<uint64_t>())
|
||||
return command_result{error::channel_limit_reached};
|
||||
|
||||
auto max_channels = this->calculate_permission(permission::i_client_max_channels, 0, false, permission_cache);
|
||||
auto max_channels = this->calculate_permission(permission::i_client_max_channels, parent_channel_id, false, permission_cache);
|
||||
if(max_channels.has_value) {
|
||||
if(!permission::v2::permission_granted(created_perm + created_semi + created_tmp + 1, max_channels))
|
||||
return command_result{permission::i_client_max_channels};
|
||||
}
|
||||
|
||||
if (cmd[0]["channel_flag_permanent"].as<bool>()) {
|
||||
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, 0, false, permission_cache);
|
||||
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, parent_channel_id, false, permission_cache);
|
||||
|
||||
if(max_channels.has_value) {
|
||||
if(!permission::v2::permission_granted(created_perm + 1, max_channels))
|
||||
@@ -622,7 +638,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
}
|
||||
}
|
||||
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
|
||||
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, 0, false, permission_cache);
|
||||
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, parent_channel_id, false, permission_cache);
|
||||
|
||||
if(max_channels.has_value) {
|
||||
if(!permission::v2::permission_granted(created_semi + 1, max_channels))
|
||||
@@ -630,7 +646,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, 0, false, permission_cache);
|
||||
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, parent_channel_id, false, permission_cache);
|
||||
|
||||
if(max_channels.has_value) {
|
||||
if(!permission::v2::permission_granted(created_tmp + 1, max_channels))
|
||||
@@ -640,20 +656,12 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
}
|
||||
|
||||
//TODO check voice (opus etc)
|
||||
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
|
||||
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
|
||||
|
||||
//bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as<bool>(); //TODO check parents here
|
||||
{ //Checkout the parent(s)
|
||||
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
|
||||
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
|
||||
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, 0, false, permission_cache);
|
||||
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, 0, false, permission_cache);
|
||||
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, parent_channel_id, false, permission_cache);
|
||||
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, parent_channel_id, false, permission_cache);
|
||||
|
||||
if(min_channel_deep.has_value || max_channel_deep.has_value) {
|
||||
auto channel_deep = 0;
|
||||
@@ -704,8 +712,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
created_channel->properties()[property::CHANNEL_CREATED_BY] = this->getClientDatabaseId();
|
||||
|
||||
{
|
||||
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, 0, false, permission_cache);
|
||||
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, 0, false, permission_cache);
|
||||
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, parent_channel_id, false, permission_cache);
|
||||
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, parent_channel_id, false, permission_cache);
|
||||
|
||||
auto permission_manager = created_channel->permissions();
|
||||
permission_manager->set_permission(
|
||||
@@ -729,14 +737,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
|
||||
if (prop == "cpid") continue;
|
||||
if (prop == "cid") continue;
|
||||
|
||||
const auto &property = property::info<property::ChannelProperties>(prop);
|
||||
if(*property == property::CHANNEL_UNDEFINED) {
|
||||
const auto &property = property::find<property::ChannelProperties>(prop);
|
||||
if(property == property::CHANNEL_UNDEFINED) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!property->validate_input(cmd[prop].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + property->name + "')");
|
||||
if(!property.validate_input(cmd[prop].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + std::string{property.name} + "')");
|
||||
continue;
|
||||
}
|
||||
created_channel->properties()[property] = cmd[prop].as<std::string>();
|
||||
@@ -839,7 +847,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<property::PropertyDescription>> keys;
|
||||
std::deque<const property::PropertyDescription*> keys;
|
||||
bool require_write_lock = false;
|
||||
bool update_max_clients = false;
|
||||
bool update_max_family_clients = false;
|
||||
@@ -858,23 +866,23 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
if(key == "return_code")
|
||||
continue;
|
||||
|
||||
const auto &property = property::info<property::ChannelProperties>(key);
|
||||
if(*property == property::CHANNEL_UNDEFINED) {
|
||||
const auto &property = property::find<property::ChannelProperties>(key);
|
||||
if(property == property::CHANNEL_UNDEFINED) {
|
||||
logError(this->getServerId(), R"({} Tried to edit a not existing channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
logError(this->getServerId(), "{} Tried to change a channel property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!property->validate_input(cmd[key].as<string>())) {
|
||||
if(!property.validate_input(cmd[key].as<string>())) {
|
||||
logError(this->getServerId(), "{} Tried to change a channel property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(channel->properties()[*property].as<string>() == cmd[key].as<string>())
|
||||
if(channel->properties()[property].as<string>() == cmd[key].as<string>())
|
||||
continue; /* we dont need to update stuff which is the same */
|
||||
|
||||
if(key == "channel_icon_id") {
|
||||
@@ -964,7 +972,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
keys.push_back(property);
|
||||
keys.push_back(&property);
|
||||
}
|
||||
|
||||
unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock);
|
||||
@@ -984,11 +992,11 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
return command_result{error::parameter_missing};
|
||||
else
|
||||
cmd["channel_password"] = ""; /* no password set */
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_PASSWORD));
|
||||
keys.push_back(&property::describe(property::CHANNEL_PASSWORD));
|
||||
}
|
||||
if(!cmd[0].has("channel_flag_password")) {
|
||||
cmd["channel_flag_password"] = !cmd["channel_password"].string().empty();
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
|
||||
}
|
||||
|
||||
if(cmd["channel_flag_password"].as<bool>()) {
|
||||
@@ -1004,27 +1012,28 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
}
|
||||
|
||||
/* test the default channel update */
|
||||
if(cmd[0].has("channel_flag_default") || channel->defaultChannel()) {
|
||||
const auto target_will_be_default = cmd[0].has("channel_flag_default") ? cmd["channel_flag_default"].as<bool>() : channel->defaultChannel();
|
||||
if(target_will_be_default) {
|
||||
if(target_channel_type != ChannelType::permanent)
|
||||
return command_result{error::channel_default_require_permanent}; /* default channel is not allowed to be non permanent */
|
||||
|
||||
if((cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as<bool>()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) {
|
||||
cmd["channel_flag_password"] = false;
|
||||
cmd["channel_password"] = "";
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
|
||||
}
|
||||
|
||||
if(cmd[0].has("channel_flag_default")) {
|
||||
if(target_will_be_default) {
|
||||
cmd["channel_maxclients"] = -1;
|
||||
cmd["channel_flag_maxclients_unlimited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
update_max_clients = true;
|
||||
|
||||
cmd["channel_maxfamilyclients"] = -1;
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
||||
update_max_family_clients = true;
|
||||
}
|
||||
}
|
||||
@@ -1035,15 +1044,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
if(channel->properties()[property::CHANNEL_MAXCLIENTS].as<int>() != -1) {
|
||||
cmd["channel_maxclients"] = -1;
|
||||
cmd["channel_flag_maxclients_unlimited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
update_max_clients = true;
|
||||
}
|
||||
if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int>() != -1) {
|
||||
cmd["channel_maxfamilyclients"] = -1;
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
||||
update_max_family_clients = true;
|
||||
}
|
||||
}
|
||||
@@ -1066,12 +1075,12 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
cmd["channel_maxclients"] = -1;
|
||||
else
|
||||
return command_result{error::parameter_missing, "channel_maxclients"}; /* max clients must be specified */
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
|
||||
}
|
||||
|
||||
if(!cmd[0].has("channel_flag_maxclients_unlimited")) {
|
||||
cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as<int>() < 0;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
||||
}
|
||||
|
||||
if(cmd["channel_flag_maxclients_unlimited"].as<bool>() && cmd["channel_maxclients"].as<int>() != -1)
|
||||
@@ -1083,32 +1092,24 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
|
||||
/* test the max family clients parameters */
|
||||
if(update_max_family_clients) {
|
||||
//auto channel_maxfamilyclients = cmd[0].has("channel_maxfamilyclients") ? std::optional<int>{cmd["channel_maxfamilyclients"].as<int>()} : std::nullopt;
|
||||
//auto channel_flag_maxfamilyclients_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()} : std::nullopt;
|
||||
//auto channel_flag_maxfamilyclients_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()} : std::nullopt;
|
||||
|
||||
/* update actual count from flags */
|
||||
if(!cmd[0].has("channel_maxfamilyclients")) {
|
||||
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
|
||||
if(cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
||||
else
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
||||
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
|
||||
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>())
|
||||
cmd["channel_flag_maxfamilyclients_unlimited"] = false;
|
||||
else
|
||||
cmd["channel_flag_maxfamilyclients_unlimited"] = true;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED));
|
||||
} else /* not really possible */
|
||||
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* family max clients must be */
|
||||
cmd["channel_maxfamilyclients"] = -1;
|
||||
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
}
|
||||
//keep this order because this command: "channeledit cid=<x> channel_maxfamilyclients=-1" should set max family clients mode to inherited
|
||||
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
|
||||
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
|
||||
if(flag_unlimited)
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
||||
else
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
|
||||
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()) {
|
||||
cmd["channel_maxfamilyclients"] = -1;
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()) {
|
||||
cmd["channel_maxfamilyclients"] = -1;
|
||||
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
|
||||
} else {
|
||||
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* since its not unlimited or inherited, channel_maxfamilyclients must be specified */
|
||||
}
|
||||
}
|
||||
|
||||
//Update the flags from channel_maxfamilyclients if needed
|
||||
if(!cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
|
||||
auto flag_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>();
|
||||
if(flag_inherited)
|
||||
@@ -1117,6 +1118,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
|
||||
}
|
||||
|
||||
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
|
||||
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
|
||||
if(flag_unlimited)
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
||||
else
|
||||
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
|
||||
}
|
||||
|
||||
/* final checkup */
|
||||
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>() && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
|
||||
return command_result{error::channel_invalid_flags}; /* both at the same time are not possible */
|
||||
|
||||
@@ -1140,10 +1150,10 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
auto self_ref = this->ref();
|
||||
shared_ptr<BasicChannel> old_default_channel;
|
||||
deque<shared_ptr<BasicChannel>> child_channel_updated;
|
||||
for(const std::shared_ptr<property::PropertyDescription>& key : keys) {
|
||||
for(const property::PropertyDescription* key : keys) {
|
||||
if(*key == property::CHANNEL_ORDER) {
|
||||
/* TODO: May move that up because if it fails may some other props have already be applied */
|
||||
if (!channel_tree->change_order(channel, cmd[key->name]))
|
||||
if (!channel_tree->change_order(channel, cmd[std::string{key->name}]))
|
||||
return command_result{error::channel_invalid_order, "Can't change order id"};
|
||||
|
||||
if(this->server) {
|
||||
@@ -1186,7 +1196,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!cmd[key->name].as<bool>()) {
|
||||
if(!cmd[std::string{key->name}].as<bool>()) {
|
||||
old_default_channel = nullptr;
|
||||
continue;
|
||||
}
|
||||
@@ -1245,13 +1255,13 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
if(conversation_manager) {
|
||||
auto conversation = conversation_manager->get(channel->channelId());
|
||||
if(conversation)
|
||||
conversation->set_history_length(cmd[key->name]);
|
||||
conversation->set_history_length(cmd[std::string{key->name}]);
|
||||
}
|
||||
} else if(*key == property::CHANNEL_NEEDED_TALK_POWER) {
|
||||
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
}
|
||||
|
||||
channel->properties()[key] = cmd[key->name].string();
|
||||
channel->properties()[*key] = cmd[std::string{key->name}].string();
|
||||
}
|
||||
if(this->server) {
|
||||
vector<property::ChannelProperties> key_vector;
|
||||
|
||||
@@ -39,17 +39,21 @@ using namespace ts::token;
|
||||
command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) {
|
||||
CMD_REQ_SERVER;
|
||||
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
||||
shared_lock tree_lock(this->channel_lock);
|
||||
{
|
||||
shared_lock tree_lock(this->channel_lock);
|
||||
|
||||
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
|
||||
return command_result{error::client_invalid_id, ""};
|
||||
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
|
||||
return command_result{error::client_invalid_id, ""};
|
||||
|
||||
deque<shared_ptr<property::PropertyDescription>> props;
|
||||
for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
|
||||
props.push_back(property::info((property::ClientProperties) prop.type().property_index));
|
||||
deque<const property::PropertyDescription*> props;
|
||||
for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
|
||||
props.push_back(&prop.type());
|
||||
}
|
||||
|
||||
this->notifyClientUpdated(client.client, props, false);
|
||||
}
|
||||
|
||||
this->notifyClientUpdated(client.client, props, false);
|
||||
if(client.client == this && this->getType() == ClientType::CLIENT_TEAMSPEAK)
|
||||
this->subscribeChannel({this->currentChannel}, true, true); /* lets show the clients in the current channel because we've not done that while joining (speed improvement ;))*/
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -391,13 +395,13 @@ command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) {
|
||||
for (auto &elm : cmd[0].keys()) {
|
||||
if (elm == "cldbid") continue;
|
||||
|
||||
auto info = property::info<property::ClientProperties>(elm);
|
||||
if(*info == property::CLIENT_UNDEFINED) {
|
||||
const auto& info = property::find<property::ClientProperties>(elm);
|
||||
if(info == property::CLIENT_UNDEFINED) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm);
|
||||
continue;
|
||||
}
|
||||
if(!info->validate_input(cmd[elm].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', Property: '" + info->name + "')");
|
||||
if(!info.validate_input(cmd[elm].as<string>())) {
|
||||
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', Property: '" + std::string{info.name} + "')");
|
||||
continue;
|
||||
}
|
||||
(*props)[info] = cmd[elm].string();
|
||||
@@ -423,29 +427,29 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
|
||||
bool update_talk_rights = false;
|
||||
unique_ptr<lock_guard<std::recursive_mutex>> nickname_lock;
|
||||
deque<pair<property::ClientProperties, string>> keys;
|
||||
std::deque<std::pair<const property::PropertyDescription*, std::string>> keys;
|
||||
for(const auto& key : cmd[0].keys()) {
|
||||
if(key == "return_code") continue;
|
||||
if(key == "clid") continue;
|
||||
|
||||
const auto &info = property::info<property::ClientProperties>(key);
|
||||
if(*info == property::CLIENT_UNDEFINED) {
|
||||
const auto &info = property::find<property::ClientProperties>(key);
|
||||
if(info == property::CLIENT_UNDEFINED) {
|
||||
logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if((info->flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
if((info.flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!info->validate_input(cmd[key].as<string>())) {
|
||||
if(!info.validate_input(cmd[key].as<string>())) {
|
||||
logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
if(client->properties()[info].as<string>() == cmd[key].as<string>()) continue;
|
||||
if(client->properties()[&info].as<string>() == cmd[key].as<string>()) continue;
|
||||
|
||||
if (*info == property::CLIENT_DESCRIPTION) {
|
||||
if (info == property::CLIENT_DESCRIPTION) {
|
||||
if (self) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_client_modify_own_description, 1, client->getChannelId());
|
||||
} else if(client->getType() == ClientType::CLIENT_MUSIC) {
|
||||
@@ -458,16 +462,16 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
|
||||
string value = cmd["client_description"].string();
|
||||
if (count_characters(value) > 200) return command_result{error::parameter_invalid, "Invalid description length. A maximum of 200 characters is allowed!"};
|
||||
} else if (*info == property::CLIENT_IS_TALKER) {
|
||||
} else if (info == property::CLIENT_IS_TALKER) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_client_set_flag_talker, 1, client->getChannelId());
|
||||
cmd["client_is_talker"] = cmd["client_is_talker"].as<bool>();
|
||||
cmd["client_talk_request"] = 0;
|
||||
update_talk_rights = true;
|
||||
|
||||
keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker");
|
||||
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
|
||||
keys.emplace_back(&property::describe(property::CLIENT_IS_TALKER), "client_is_talker");
|
||||
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
|
||||
continue;
|
||||
} else if(*info == property::CLIENT_NICKNAME) {
|
||||
} else if(info == property::CLIENT_NICKNAME) {
|
||||
if(!self) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
@@ -500,7 +504,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if(*info == property::CLIENT_PLAYER_VOLUME) {
|
||||
} else if(info == property::CLIENT_PLAYER_VOLUME) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
||||
@@ -515,7 +519,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
return command_result{permission::i_client_music_create_modify_max_volume};
|
||||
|
||||
bot->volume_modifier(cmd["player_volume"]);
|
||||
} else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) {
|
||||
} else if(info == property::CLIENT_IS_CHANNEL_COMMANDER) {
|
||||
if(!self) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
@@ -525,7 +529,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
|
||||
if(cmd["client_is_channel_commander"].as<bool>())
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_client_use_channel_commander, 1, client->getChannelId());
|
||||
} else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
||||
} else if(info == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
||||
//FIXME allow other to remove this thing
|
||||
if(!self) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC)
|
||||
@@ -544,7 +548,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
cmd["client_talk_request"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
else
|
||||
cmd["client_talk_request"] = 0;
|
||||
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
|
||||
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
|
||||
continue;
|
||||
} else if (self && key == "client_badges") {
|
||||
std::string str = cmd[key];
|
||||
@@ -576,7 +580,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
||||
}
|
||||
} else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) {
|
||||
} else if(!self && (info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) {
|
||||
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
||||
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
||||
@@ -596,8 +600,8 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
cmd["client_lastconnected"] = value;
|
||||
}
|
||||
|
||||
keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected");
|
||||
} else if(!self && *info == property::CLIENT_BOT_TYPE) {
|
||||
keys.emplace_back(&property::describe(property::CLIENT_LASTCONNECTED), "client_lastconnected");
|
||||
} else if(!self && info == property::CLIENT_BOT_TYPE) {
|
||||
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
||||
auto type = cmd["client_bot_type"].as<MusicClient::Type::value>();
|
||||
if(type == MusicClient::Type::TEMPORARY) {
|
||||
@@ -608,7 +612,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_permanent, 1, client->getChannelId());
|
||||
} else
|
||||
return command_result{error::parameter_invalid};
|
||||
} else if(*info == property::CLIENT_AWAY_MESSAGE) {
|
||||
} else if(info == property::CLIENT_AWAY_MESSAGE) {
|
||||
if(!self) continue;
|
||||
|
||||
if(cmd["client_away_message"].string().length() > 256)
|
||||
@@ -617,12 +621,12 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.emplace_back((property::ClientProperties) info->property_index, key);
|
||||
keys.emplace_back(&info, key);
|
||||
}
|
||||
|
||||
deque<property::ClientProperties> updates;
|
||||
deque<const property::PropertyDescription*> updates;
|
||||
for(const auto& key : keys) {
|
||||
if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
||||
if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
||||
client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as<bool>() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
|
||||
}
|
||||
client->properties()[key.first] = cmd[0][key.second].value();
|
||||
@@ -1116,7 +1120,7 @@ command_result ConnectedClient::handleCommandClientInfo(Command &cmd) {
|
||||
}
|
||||
|
||||
for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
|
||||
res[result_index][key.type().name] = key.value();
|
||||
res[result_index][std::string{key.type().name}] = key.value();
|
||||
if(view_remote)
|
||||
res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as<string>();
|
||||
else
|
||||
|
||||
@@ -137,6 +137,10 @@ inline bool permission_require_granted_value(ts::permission::PermissionType type
|
||||
case permission::i_client_max_permanent_channels:
|
||||
case permission::i_client_max_semi_channels:
|
||||
case permission::i_client_max_temporary_channels:
|
||||
case permission::i_channel_create_modify_with_temp_delete_delay:
|
||||
case permission::i_client_talk_power:
|
||||
case permission::i_client_needed_talk_power:
|
||||
case permission::b_channel_create_with_needed_talk_power:
|
||||
|
||||
case permission::i_channel_max_depth:
|
||||
case permission::i_channel_min_depth:
|
||||
|
||||
@@ -366,7 +366,7 @@ command_result ConnectedClient::handleCommandPermissionList(Command &cmd) {
|
||||
|
||||
#define M(ptype) \
|
||||
do { \
|
||||
for(const auto& prop : property::impl::list<ptype>()) { \
|
||||
for(const auto& prop : property::list<ptype>()) { \
|
||||
if((prop->flags & property::FLAG_INTERNAL) > 0) continue; \
|
||||
response[index]["name"] = prop->name; \
|
||||
response[index]["flags"] = prop->flags; \
|
||||
@@ -767,7 +767,9 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
|
||||
CMD_RESET_IDLE;
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
||||
|
||||
string uid;
|
||||
std::string target_unique_id{};
|
||||
ClientDbId target_database_id{0};
|
||||
|
||||
string reason = cmd[0].has("banreason") ? cmd["banreason"].string() : "";
|
||||
auto time = cmd[0].has("time") ? cmd["time"].as<uint64_t>() : 0UL;
|
||||
chrono::time_point<chrono::system_clock> until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point<chrono::system_clock>();
|
||||
@@ -776,43 +778,40 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
|
||||
const auto no_hwid = cmd.hasParm("no-hardware-id");
|
||||
const auto no_ip = cmd.hasParm("no-ip");
|
||||
|
||||
deque<shared_ptr<ConnectedClient>> target_clients;
|
||||
std::deque<std::shared_ptr<ConnectedClient>> target_clients;
|
||||
if (cmd[0].has("uid")) {
|
||||
target_clients = this->server->findClientsByUid(uid = cmd["uid"].string());
|
||||
for(const auto& client : target_clients)
|
||||
if(client->getType() == ClientType::CLIENT_MUSIC)
|
||||
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
|
||||
target_clients = this->server->findClientsByUid(target_unique_id = cmd["uid"].string());
|
||||
} else if(cmd[0].has("cldbid")) {
|
||||
target_clients = this->server->findClientsByCldbId(target_database_id = cmd["cldbid"].as<ClientDbId>());
|
||||
} else {
|
||||
target_clients = {this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
||||
if(!target_clients[0]) {
|
||||
return command_result{error::client_invalid_id, "Could not find target client"};
|
||||
}
|
||||
if(target_clients[0]->getType() == ClientType::CLIENT_MUSIC) {
|
||||
}
|
||||
|
||||
for(const auto& client : target_clients)
|
||||
if(client->getType() == ClientType::CLIENT_MUSIC)
|
||||
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
|
||||
}
|
||||
uid = target_clients[0]->getUid();
|
||||
}
|
||||
|
||||
ClientDbId target_dbid = 0;
|
||||
if (!target_clients.empty()) {
|
||||
target_dbid = target_clients[0]->getClientDatabaseId();
|
||||
} else {
|
||||
auto info = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->getServer(), {uid});
|
||||
if (!info.empty())
|
||||
target_dbid = info[0]->cldbid;
|
||||
else
|
||||
return command_result{error::client_unknown};
|
||||
}
|
||||
if(!target_clients.empty()) {
|
||||
if(target_unique_id.empty())
|
||||
target_unique_id = target_clients.back()->getUid();
|
||||
|
||||
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
|
||||
if(!target_database_id)
|
||||
target_database_id = target_clients.back()->getClientDatabaseId();
|
||||
}
|
||||
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
|
||||
return command_result{permission::i_client_ban_power};
|
||||
|
||||
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0)))
|
||||
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0)))
|
||||
return command_result{permission::b_client_ignore_bans};
|
||||
|
||||
deque<BanId> ban_ids;
|
||||
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until);
|
||||
ban_ids.push_back(_id);
|
||||
if(!target_unique_id.empty()) {
|
||||
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, target_unique_id, "", "", "", until);
|
||||
ban_ids.push_back(_id);
|
||||
}
|
||||
|
||||
auto b_ban_name = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_name, 0), false);
|
||||
auto b_ban_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_ip, 0), false);
|
||||
@@ -1329,7 +1328,9 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_find, 1);
|
||||
|
||||
deque<pair<pair<string, permission::PermissionType>, bool>> permissions;
|
||||
std::vector<std::tuple<std::string, permission::PermissionType, bool>> requested_permissions{};
|
||||
requested_permissions.reserve(cmd.bulkCount());
|
||||
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission;
|
||||
for(size_t index = 0; index < cmd.bulkCount(); index++) {
|
||||
bool granted = false;
|
||||
@@ -1349,22 +1350,21 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
permissions.emplace_back(pair<pair<string, permission::PermissionType>, bool>{{permission->name, permission->type}, granted});
|
||||
requested_permissions.emplace_back(permission->name, permission->type, granted);
|
||||
}
|
||||
|
||||
if(permissions.empty())
|
||||
if(requested_permissions.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
map<string, uint8_t> flags;
|
||||
map<string, permission::PermissionType> quick_mapping;
|
||||
string query_string;
|
||||
for(const auto& entry : permissions) {
|
||||
if(flags[entry.first.first] == 0) {
|
||||
quick_mapping[entry.first.first] = entry.first.second;
|
||||
query_string += string(query_string.empty() ? "" : " OR ") + "`permId` = '" + entry.first.first + "'";
|
||||
std::map<std::string, std::tuple<permission::PermissionType, uint8_t>> db_lookup_mapping{};
|
||||
std::string query_string{};
|
||||
for(const auto& [name, id, as_granted] : requested_permissions) {
|
||||
auto& mapping = db_lookup_mapping[name];
|
||||
if(std::get<0>(mapping) == 0) {
|
||||
std::get<0>(mapping) = id;
|
||||
query_string += std::string{query_string.empty() ? "" : " OR "} + "`permId` = '" + name + "'";
|
||||
}
|
||||
|
||||
flags[entry.first.first] |= entry.second ? 2 : 1;
|
||||
std::get<1>(mapping) |= (1U << as_granted);
|
||||
}
|
||||
|
||||
deque<unique_ptr<PermissionEntry>> entries;
|
||||
@@ -1373,13 +1373,14 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
variable{":sid", this->server->getServerId()},
|
||||
variable{":playlist", permission::SQL_PERM_PLAYLIST}
|
||||
).query([&](int length, string* values, string* columns) {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_GROUP;
|
||||
uint64_t id = 0;
|
||||
ChannelId channel_id = 0;
|
||||
permission::PermissionValue value = 0,
|
||||
granted_value = 0;
|
||||
string permission_name;
|
||||
permission::PermissionSqlType type{permission::SQL_PERM_GROUP};
|
||||
uint64_t id{0};
|
||||
ChannelId channel_id{0};
|
||||
permission::PermissionValue value{0}, granted_value{0};
|
||||
string permission_name{};
|
||||
bool negate = false, skip = false;
|
||||
|
||||
#if 0
|
||||
for (int index = 0; index < length; index++) {
|
||||
try {
|
||||
if(columns[index] == "type")
|
||||
@@ -1403,35 +1404,48 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
assert(length == 8);
|
||||
try {
|
||||
type = static_cast<permission::PermissionSqlType>(stoll(values[1]));
|
||||
permission_name = values[0];
|
||||
id = static_cast<uint64_t>(stoll(values[2]));
|
||||
channel_id = static_cast<ChannelId>(stoll(values[3]));
|
||||
value = static_cast<permission::PermissionValue>(stoll(values[4]));
|
||||
granted_value = static_cast<permission::PermissionValue>(stoll(values[5]));
|
||||
negate = values[7] == "1";
|
||||
skip = values[6] == "1";
|
||||
} catch(std::exception& ex) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto request = db_lookup_mapping.find(permission_name);
|
||||
if(request == db_lookup_mapping.end()) return 0; /* shall not happen */
|
||||
|
||||
auto flags = std::get<1>(request->second);
|
||||
/* value */
|
||||
if((flags[permission_name] & 0x1) > 0 && value > 0) {
|
||||
if((flags & 0x1U) > 0 && value > 0) {
|
||||
auto result = make_unique<PermissionEntry>();
|
||||
result->permission_type = quick_mapping[permission_name];
|
||||
result->permission_type = std::get<0>(request->second);
|
||||
result->permission_value = value;
|
||||
result->type = type;
|
||||
result->channel_id = channel_id;
|
||||
result->negate = negate;
|
||||
result->skip = skip;
|
||||
if (type == permission::SQL_PERM_GROUP) {
|
||||
auto gr = this->server->groups->findGroup(id);
|
||||
if (!gr) return 0;
|
||||
|
||||
result->group_id = id;
|
||||
if(gr->target() == GROUPTARGET_CHANNEL)
|
||||
result->channel_id = 1;
|
||||
} else if(type == permission::SQL_PERM_USER) {
|
||||
result->client_id = id;
|
||||
}
|
||||
|
||||
if(result)
|
||||
entries.push_back(std::move(result));
|
||||
entries.push_back(std::move(result));
|
||||
}
|
||||
|
||||
/* granted */
|
||||
if((flags[permission_name] & 0x2) > 0 && granted_value > 0) {
|
||||
if((flags & 0x2U) > 0 && granted_value > 0) {
|
||||
auto result = make_unique<PermissionEntry>();
|
||||
result->permission_type = (permission::PermissionType) (quick_mapping[permission_name] | PERM_ID_GRANT);
|
||||
result->permission_type = (permission::PermissionType) (std::get<0>(request->second) | PERM_ID_GRANT);
|
||||
result->permission_value = granted_value;
|
||||
result->type = type;
|
||||
result->channel_id = channel_id;
|
||||
@@ -1439,21 +1453,17 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
result->skip = skip;
|
||||
|
||||
if (type == permission::SQL_PERM_GROUP) {
|
||||
auto gr = this->server->groups->findGroup(id);
|
||||
if (!gr) return 0;
|
||||
|
||||
result->group_id = id;
|
||||
if(gr->target() == GROUPTARGET_CHANNEL)
|
||||
result->channel_id = 1;
|
||||
} else if(type == permission::SQL_PERM_USER) {
|
||||
result->client_id = id;
|
||||
}
|
||||
|
||||
if(result)
|
||||
entries.push_back(std::move(result));
|
||||
entries.push_back(std::move(result));
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
if(entries.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
struct CommandPerm {
|
||||
permission::PermissionType p;
|
||||
@@ -1465,13 +1475,13 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
|
||||
std::vector<CommandPerm> perms;
|
||||
perms.resize(entries.size());
|
||||
size_t index = 0;
|
||||
|
||||
size_t index{0};
|
||||
auto all_groups = this->server->groups->availableGroups(true);
|
||||
for(const auto& entry : entries) {
|
||||
auto& perm = perms[index++];
|
||||
|
||||
perm.p = entry->permission_type;
|
||||
perm.v = entry->permission_value;
|
||||
|
||||
#if 0 /* TS3 switched the oder and YatQa as well, to keep compatibility we do it as well */
|
||||
if(entry->type == permission::SQL_PERM_USER) {
|
||||
if(entry->channel_id > 0) {
|
||||
perm.id1 = entry->client_id;
|
||||
@@ -1497,7 +1507,42 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
perm.t = 0; /* server group */
|
||||
}
|
||||
}
|
||||
#else
|
||||
if(entry->type == permission::SQL_PERM_USER) {
|
||||
if(entry->channel_id > 0) {
|
||||
perm.id1 = entry->channel_id;
|
||||
perm.id2 = entry->client_id;
|
||||
perm.t = 4; /* client channel */
|
||||
} else {
|
||||
perm.id1 = entry->client_id;
|
||||
perm.id2 = 0;
|
||||
perm.t = 1; /* client server */
|
||||
}
|
||||
} else if(entry->type == permission::SQL_PERM_CHANNEL) {
|
||||
perm.id1 = entry->channel_id;
|
||||
perm.id2 = 0;
|
||||
perm.t = 2; /* channel permission */
|
||||
} else if(entry->type == permission::SQL_PERM_GROUP) {
|
||||
auto group = std::find_if(all_groups.begin(), all_groups.end(), [&](const auto& group) { return group->groupId() == entry->group_id; });
|
||||
if(group == all_groups.end()) {
|
||||
index--; /* unknown group */
|
||||
continue;
|
||||
}
|
||||
if((*group)->target() == GroupTarget::GROUPTARGET_CHANNEL) {
|
||||
perm.id1 = 0;
|
||||
perm.id2 = entry->group_id;
|
||||
perm.t = 3; /* channel group */
|
||||
} else {
|
||||
perm.id1 = entry->group_id;
|
||||
perm.id2 = 0;
|
||||
perm.t = 0; /* server group */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
perm.p = entry->permission_type;
|
||||
perm.v = entry->permission_value;
|
||||
}
|
||||
perms.erase(perms.begin() + index, perms.end());
|
||||
|
||||
|
||||
sort(perms.begin(), perms.end(), [](const CommandPerm& a, const CommandPerm& b) {
|
||||
@@ -1516,6 +1561,7 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
return &a > &b;
|
||||
});
|
||||
|
||||
#if 0
|
||||
Command result(this->notify_response_command("notifypermfind"));
|
||||
index = 0;
|
||||
|
||||
@@ -1528,9 +1574,22 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
|
||||
result[index]["t"] = e.t;
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index == 0) return command_result{error::database_empty_result};
|
||||
this->sendCommand(result);
|
||||
#else
|
||||
command_builder result{this->notify_response_command("notifypermfind"), 64, perms.size()};
|
||||
index = 0;
|
||||
|
||||
for(const auto& e : perms) {
|
||||
auto bulk = result.bulk(index++);
|
||||
bulk.put("t", e.t);
|
||||
bulk.put("p", (uint16_t) e.p);
|
||||
bulk.put("v", e.v);
|
||||
bulk.put("id1", e.id1);
|
||||
bulk.put("id2", e.id2);
|
||||
}
|
||||
this->sendCommand(result);
|
||||
#endif
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -2088,6 +2147,7 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
|
||||
OptionalServerId server_id = this->getServerId();
|
||||
if(cmd[0].has("server_id"))
|
||||
server_id = cmd["server_id"];
|
||||
|
||||
if(cmd[0].has("sid"))
|
||||
server_id = cmd["sid"];
|
||||
|
||||
@@ -2095,24 +2155,43 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
|
||||
if(!server && server_id != EmptyServerId && server_id != 0)
|
||||
return command_result{error::server_invalid_id};
|
||||
|
||||
if(server) {
|
||||
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create};
|
||||
} else {
|
||||
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create};
|
||||
}
|
||||
|
||||
auto username = cmd["client_login_name"].as<string>();
|
||||
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
|
||||
|
||||
if(password.empty())
|
||||
password = rnd_string(QUERY_PASSWORD_LENGTH);
|
||||
|
||||
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
|
||||
if(account) return command_result{error::query_already_exists};
|
||||
|
||||
account = serverInstance->getQueryServer()->create_query_account(username, server_id, this->getUid(), password);
|
||||
std::string uid = this->getUid();
|
||||
if(cmd[0].has("cldbid")){
|
||||
if(!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"].as<ClientDbId>()))
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
if(server) {
|
||||
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create};
|
||||
} else {
|
||||
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create};
|
||||
}
|
||||
|
||||
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
|
||||
if(info.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
uid = info[0]->uniqueId;
|
||||
} else {
|
||||
if(server) {
|
||||
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create_own};
|
||||
} else {
|
||||
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
|
||||
return command_result{permission::b_client_query_create_own};
|
||||
}
|
||||
}
|
||||
|
||||
if(password.empty())
|
||||
password = rnd_string(QUERY_PASSWORD_LENGTH);
|
||||
|
||||
account = serverInstance->getQueryServer()->create_query_account(username, server_id, uid, password);
|
||||
if(!account)
|
||||
return command_result{error::vs_critical};
|
||||
|
||||
@@ -2262,7 +2341,7 @@ command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) {
|
||||
if(geoloc::provider) {
|
||||
auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false);
|
||||
if(loc) {
|
||||
logError(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
|
||||
logMessage(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
|
||||
this->properties()[property::CLIENT_COUNTRY] = loc->identifier;
|
||||
server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_COUNTRY});
|
||||
new_country = loc->identifier;
|
||||
|
||||
@@ -447,41 +447,41 @@ command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) {
|
||||
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_modify_power, permission::i_playlist_modify_power); perr)
|
||||
return command_result{perr};
|
||||
|
||||
deque<pair<shared_ptr<property::PropertyDescription>, string>> properties;
|
||||
deque<pair<const property::PropertyDescription*, string>> properties;
|
||||
|
||||
for(const auto& key : cmd[0].keys()) {
|
||||
if(key == "playlist_id") continue;
|
||||
if(key == "return_code") continue;
|
||||
|
||||
auto property = property::info<property::PlaylistProperties>(key);
|
||||
if(*property == property::PLAYLIST_UNDEFINED) {
|
||||
const auto& property = property::find<property::PlaylistProperties>(key);
|
||||
if(property == property::PLAYLIST_UNDEFINED) {
|
||||
logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
|
||||
logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!property->validate_input(cmd[key].as<string>())) {
|
||||
if(!property.validate_input(cmd[key].as<string>())) {
|
||||
logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(*property == property::PLAYLIST_CURRENT_SONG_ID) {
|
||||
if(property == property::PLAYLIST_CURRENT_SONG_ID) {
|
||||
auto song_id = cmd[key].as<SongId>();
|
||||
auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr;
|
||||
if(song_id != 0 && !song)
|
||||
return command_result{error::playlist_invalid_song_id};
|
||||
} else if(*property == property::PLAYLIST_MAX_SONGS) {
|
||||
} else if(property == property::PLAYLIST_MAX_SONGS) {
|
||||
auto value = cmd[key].as<int32_t>();
|
||||
auto max_value = this->calculate_permission(permission::i_max_playlist_size, this->getChannelId());
|
||||
if(max_value.has_value && !permission::v2::permission_granted(value, max_value))
|
||||
return command_result{permission::i_max_playlist_size};
|
||||
}
|
||||
|
||||
properties.emplace_back(property, key);
|
||||
properties.emplace_back(&property, key);
|
||||
}
|
||||
for(const auto& property : properties) {
|
||||
if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) {
|
||||
@@ -801,6 +801,44 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
constexpr auto max_song_meta_info = 1024 * 512;
|
||||
|
||||
inline size_t estimated_song_info_size(const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
|
||||
return 128 + std::min(song->metadata.json_string.length(), (size_t) max_song_meta_info) + extract_metadata * 256;
|
||||
}
|
||||
|
||||
inline void fill_song_info(ts::command_builder_bulk bulk, const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
|
||||
bulk.reserve(estimated_song_info_size(song, extract_metadata));
|
||||
|
||||
bulk.put("song_id", song->song_id);
|
||||
bulk.put("song_invoker", song->invoker);
|
||||
bulk.put("song_previous_song_id", song->previous_song_id);
|
||||
bulk.put("song_url", song->original_url);
|
||||
bulk.put("song_url_loader", song->url_loader);
|
||||
bulk.put("song_loaded", song->metadata.is_loaded());
|
||||
if(song->metadata.json_string.length() > 1024 * 1024 * 512) {
|
||||
logWarning(LOG_GENERAL, "Dropping song metadata because its way to big. ({}bytes)", song->metadata.json_string.size());
|
||||
} else {
|
||||
bulk.put("song_metadata", song->metadata.json_string);
|
||||
}
|
||||
|
||||
if(extract_metadata) {
|
||||
bulk.reserve(256, true);
|
||||
auto metadata = song->metadata.loaded_data;
|
||||
if(extract_metadata && song->metadata.is_loaded() && metadata) {
|
||||
bulk.put("song_metadata_title", metadata->title);
|
||||
bulk.put("song_metadata_description", metadata->description);
|
||||
bulk.put("song_metadata_url", metadata->url);
|
||||
bulk.put("song_metadata_length", metadata->length.count());
|
||||
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
|
||||
bulk.put("song_metadata_thumbnail_url", thumbnail->url());
|
||||
} else {
|
||||
bulk.put("song_metadata_thumbnail_url", "none");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) {
|
||||
CMD_REF_SERVER(ref_server);
|
||||
CMD_RESET_IDLE;
|
||||
@@ -816,39 +854,32 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd)
|
||||
if(songs.empty())
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
Command notify(this->notify_response_command("notifyplaylistsonglist"));
|
||||
notify["playlist_id"] = playlist->playlist_id();
|
||||
|
||||
ts::command_builder result{this->notify_response_command("notifyplaylistsonglist")};
|
||||
result.put(0, "version", 2); /* to signalize that we're sending the response bulked */
|
||||
auto extract_metadata = cmd.hasParm("extract-metadata");
|
||||
|
||||
size_t index = 0;
|
||||
size_t index{0};
|
||||
for(const auto& song : songs) {
|
||||
notify[index]["song_id"] = song->song_id;
|
||||
notify[index]["song_invoker"] = song->invoker;
|
||||
notify[index]["song_previous_song_id"] = song->previous_song_id;
|
||||
notify[index]["song_url"] = song->original_url;
|
||||
notify[index]["song_url_loader"] = song->url_loader;
|
||||
notify[index]["song_loaded"] = song->metadata.is_loaded();
|
||||
notify[index]["song_metadata"] = song->metadata.json_string;
|
||||
|
||||
if(extract_metadata) {
|
||||
auto metadata = song->metadata.loaded_data;
|
||||
if(extract_metadata && song->metadata.is_loaded() && metadata) {
|
||||
notify[index]["song_metadata_title"] = metadata->title;
|
||||
notify[index]["song_metadata_description"] = metadata->description;
|
||||
notify[index]["song_metadata_url"] = metadata->url;
|
||||
notify[index]["song_metadata_length"] = metadata->length.count();
|
||||
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
|
||||
notify[index]["song_metadata_thumbnail_url"] = thumbnail->url();
|
||||
} else {
|
||||
notify[index]["song_metadata_thumbnail_url"] = "none";
|
||||
}
|
||||
}
|
||||
if(index == 0) {
|
||||
result.put(0, "playlist_id", playlist->playlist_id());
|
||||
}
|
||||
fill_song_info(result.bulk(index), song, extract_metadata);
|
||||
|
||||
index++;
|
||||
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK && result.current_size() + estimated_song_info_size(song, extract_metadata) > 128 * 1024) {
|
||||
this->sendCommand(result);
|
||||
result.reset();
|
||||
index = 0;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
if(index > 0)
|
||||
this->sendCommand(result);
|
||||
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
ts::command_builder finish{"notifyplaylistsonglistfinished"};
|
||||
finish.put(0, "playlist_id", playlist->playlist_id());
|
||||
this->sendCommand(finish);
|
||||
}
|
||||
this->sendCommand(notify);
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@@ -224,14 +224,14 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
|
||||
std::deque<std::string> keys;
|
||||
bool group_update = false;
|
||||
for (const auto& elm : toApplay) {
|
||||
auto info = property::impl::info<property::VirtualServerProperties>(elm.first);
|
||||
if(*info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
const auto& info = property::find<property::VirtualServerProperties>(elm.first);
|
||||
if(info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
logCritical(target_server ? target_server->getServerId() : 0, "Missing server property " + elm.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!info->validate_input(elm.second)) {
|
||||
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + info->name + "')");
|
||||
if(!info.validate_input(elm.second)) {
|
||||
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')");
|
||||
continue;
|
||||
}
|
||||
if(target_server)
|
||||
@@ -240,7 +240,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
|
||||
(*serverInstance->getDefaultServerProperties())[info] = elm.second;
|
||||
keys.push_back(elm.first);
|
||||
|
||||
group_update |= *info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || *info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || *info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
|
||||
group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
|
||||
}
|
||||
|
||||
if(target_server) {
|
||||
@@ -262,35 +262,39 @@ command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command
|
||||
CMD_REQ_SERVER;
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_connectioninfo_view, 1);
|
||||
|
||||
Command notify("notifyserverconnectioninfo");
|
||||
ts::command_builder result{"notifyserverconnectioninfo"};
|
||||
auto first_bulk = result.bulk(0);
|
||||
|
||||
auto statistics = this->server->getServerStatistics()->statistics();
|
||||
auto report = this->server->getServerStatistics()->dataReport();
|
||||
auto total_stats = this->server->getServerStatistics()->total_stats();
|
||||
auto minute_report = this->server->getServerStatistics()->minute_stats();
|
||||
auto second_report = this->server->getServerStatistics()->second_stats();
|
||||
auto network_report = this->server->generate_network_report();
|
||||
|
||||
notify[0]["connection_filetransfer_bandwidth_sent"] = report.file_send;
|
||||
notify[0]["connection_filetransfer_bandwidth_received"] = report.file_recv;
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, minute_report.file_bytes_sent);
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, minute_report.file_bytes_received);
|
||||
|
||||
notify[0]["connection_filetransfer_bytes_sent_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
|
||||
notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, minute_report.file_bytes_sent);
|
||||
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, minute_report.file_bytes_received);
|
||||
|
||||
notify[0]["connection_filetransfer_bytes_sent_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>();
|
||||
notify[0]["connection_filetransfer_bytes_received_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>();
|
||||
first_bulk.put_unchecked("connection_filetransfer_bytes_sent_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>());
|
||||
first_bulk.put_unchecked("connection_filetransfer_bytes_received_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>());
|
||||
|
||||
notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
|
||||
notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
|
||||
notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
|
||||
notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
|
||||
first_bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_TOTAL, std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_BYTES_SENT_TOTAL, std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_TOTAL, std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_TOTAL, std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), 0U));
|
||||
|
||||
notify[0]["connection_bandwidth_sent_last_second_total"] = report.send_second;
|
||||
notify[0]["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
|
||||
notify[0]["connection_bandwidth_received_last_second_total"] = report.recv_second;
|
||||
notify[0]["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
|
||||
|
||||
notify[0]["connection_connected_time"] = this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>();
|
||||
notify[0]["connection_packetloss_total"] = this->server->averagePacketLoss();
|
||||
notify[0]["connection_ping"] = this->server->averagePing();
|
||||
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_sent.begin(), second_report.connection_bytes_sent.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_sent.begin(), minute_report.connection_bytes_sent.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_received.begin(), second_report.connection_bytes_received.end(), 0U));
|
||||
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_received.begin(), minute_report.connection_bytes_received.end(), 0U));
|
||||
|
||||
this->sendCommand(notify);
|
||||
first_bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>());
|
||||
first_bulk.put_unchecked(property::CONNECTION_PACKETLOSS_TOTAL, network_report.average_loss);
|
||||
first_bulk.put_unchecked(property::CONNECTION_PING, network_report.average_ping);
|
||||
|
||||
this->sendCommand(result);
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace ts::server {
|
||||
bool notifyServerUpdated(std::shared_ptr<ConnectedClient> ptr) override;
|
||||
bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg) override;
|
||||
|
||||
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<std::shared_ptr<property::PropertyDescription>> &deque, bool lock_channel_tree) override;
|
||||
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<const property::PropertyDescription*> &deque, bool lock_channel_tree) override;
|
||||
|
||||
bool notifyPluginCmd(std::string name, std::string msg,std::shared_ptr<ConnectedClient>) override;
|
||||
bool notifyClientChatComposing(const std::shared_ptr<ConnectedClient> &ptr) override;
|
||||
@@ -171,6 +171,7 @@ namespace ts::server {
|
||||
command_result handleCommandServerIdGetByPort(Command&);
|
||||
|
||||
command_result handleCommandServerSnapshotDeploy(Command&);
|
||||
command_result handleCommandServerSnapshotDeployNew(const command_parser&);
|
||||
command_result handleCommandServerSnapshotCreate(Command&);
|
||||
command_result handleCommandServerProcessStop(Command&);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <misc/base64.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include <numeric>
|
||||
|
||||
#include "src/client/command_handler/helpers.h"
|
||||
|
||||
@@ -99,9 +100,14 @@ command_result QueryClient::handleCommand(Command& cmd) {
|
||||
return this->handleCommandHostInfo(cmd);
|
||||
case string_hash("bindinglist"):
|
||||
return this->handleCommandBindingList(cmd);
|
||||
case string_hash("serversnapshotdeploy"):
|
||||
return this->handleCommandServerSnapshotDeploy(cmd);
|
||||
case string_hash("serversnapshotdeploy"): {
|
||||
//return this->handleCommandServerSnapshotDeploy(cmd);
|
||||
auto cmd_str = cmd.build();
|
||||
ts::command_parser parser{cmd_str};
|
||||
if(!parser.parse(true)) return command_result{error::vs_critical};
|
||||
|
||||
return this->handleCommandServerSnapshotDeployNew(parser);
|
||||
}
|
||||
case string_hash("serversnapshotcreate"):
|
||||
return this->handleCommandServerSnapshotCreate(cmd);
|
||||
case string_hash("serverprocessstop"):
|
||||
@@ -397,37 +403,36 @@ command_result QueryClient::handleCommandServerInfo(Command &) {
|
||||
|
||||
|
||||
if(this->server && permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_connectioninfo_view, 0))) {
|
||||
auto stats = this->server->getServerStatistics()->statistics();
|
||||
auto report = this->server->serverStatistics->dataReport();
|
||||
cmd["connection_bandwidth_sent_last_second_total"] = report.send_second;
|
||||
cmd["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
|
||||
cmd["connection_bandwidth_received_last_second_total"] = report.recv_second;
|
||||
cmd["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
|
||||
auto total_stats = this->server->getServerStatistics()->total_stats();
|
||||
auto report_second = this->server->serverStatistics->second_stats();
|
||||
auto report_minute = this->server->serverStatistics->minute_stats();
|
||||
cmd["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
|
||||
cmd["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), 0U);
|
||||
cmd["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
|
||||
cmd["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), 0U);
|
||||
|
||||
cmd["connection_filetransfer_bandwidth_sent"] = report.file_send;
|
||||
cmd["connection_filetransfer_bandwidth_received"] = report.file_recv;
|
||||
cmd["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
|
||||
cmd["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
|
||||
cmd["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
|
||||
cmd["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
|
||||
|
||||
cmd["connection_packets_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT].value();
|
||||
cmd["connection_bytes_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED].value();
|
||||
cmd["connection_packets_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].value();
|
||||
cmd["connection_bytes_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].value();
|
||||
cmd["connection_packets_sent_speech"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
cmd["connection_bytes_sent_speech"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
|
||||
cmd["connection_packets_received_speech"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE];
|
||||
cmd["connection_bytes_received_speech"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE];
|
||||
|
||||
cmd["connection_packets_sent_keepalive"] = (*stats)[property::CONNECTION_PACKETS_SENT_KEEPALIVE].value();
|
||||
cmd["connection_packets_received_keepalive"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE].value();
|
||||
cmd["connection_bytes_received_keepalive"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_KEEPALIVE].value();
|
||||
cmd["connection_bytes_sent_keepalive"] = (*stats)[property::CONNECTION_BYTES_SENT_KEEPALIVE].value();
|
||||
cmd["connection_packets_sent_keepalive"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
|
||||
cmd["connection_packets_received_keepalive"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
|
||||
cmd["connection_bytes_received_keepalive"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
|
||||
cmd["connection_bytes_sent_keepalive"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
|
||||
|
||||
cmd["connection_packets_sent_control"] = (*stats)[property::CONNECTION_PACKETS_SENT_CONTROL].value();
|
||||
cmd["connection_bytes_sent_control"] = (*stats)[property::CONNECTION_BYTES_SENT_CONTROL].value();
|
||||
cmd["connection_packets_received_control"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_CONTROL].value();
|
||||
cmd["connection_bytes_received_control"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_CONTROL].value();
|
||||
cmd["connection_packets_sent_control"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
cmd["connection_bytes_sent_control"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
|
||||
cmd["connection_packets_received_control"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND];
|
||||
cmd["connection_bytes_received_control"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND];
|
||||
|
||||
cmd["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].value();
|
||||
cmd["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].value();
|
||||
cmd["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].value();
|
||||
cmd["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].value();
|
||||
cmd["connection_packets_sent_total"] = std::accumulate(report_second.connection_packets_sent.begin(), report_second.connection_packets_sent.end(), 0U);
|
||||
cmd["connection_bytes_sent_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
|
||||
cmd["connection_packets_received_total"] = std::accumulate(report_second.connection_packets_received.begin(), report_second.connection_packets_received.end(), 0U);
|
||||
cmd["connection_bytes_received_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
|
||||
} else {
|
||||
cmd["connection_bandwidth_sent_last_second_total"] = "0";
|
||||
cmd["connection_bandwidth_sent_last_minute_total"] = "0";
|
||||
@@ -598,12 +603,12 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
|
||||
if(key == "virtualserver_port") continue;
|
||||
if(key == "virtualserver_host") continue;
|
||||
|
||||
auto info = property::impl::info<property::VirtualServerProperties>(key);
|
||||
if(*info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
const auto& info = property::find<property::VirtualServerProperties>(key);
|
||||
if(info == property::VIRTUALSERVER_UNDEFINED) {
|
||||
logError(server->getServerId(), "Tried to change unknown server property " + key);
|
||||
continue;
|
||||
}
|
||||
if(!info->validate_input(cmd[key].as<string>())) {
|
||||
if(!info.validate_input(cmd[key].as<string>())) {
|
||||
logError(server->getServerId(), "Tried to change " + key + " to an invalid value: " + cmd[key].as<string>());
|
||||
continue;
|
||||
}
|
||||
@@ -722,9 +727,9 @@ command_result QueryClient::handleCommandInstanceEdit(Command& cmd) {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_modify_settings, 1);
|
||||
|
||||
for(const auto &key : cmd[0].keys()){
|
||||
auto info = property::impl::info<property::InstanceProperties>(key);
|
||||
const auto* info = &property::find<property::InstanceProperties>(key);
|
||||
if(key == "serverinstance_serverquery_max_connections_per_ip")
|
||||
info = property::impl::info(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
|
||||
info = &property::describe(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
|
||||
|
||||
if(*info == property::SERVERINSTANCE_UNDEFINED) {
|
||||
logError(LOG_QUERY, "Query {} tried to change a non existing instance property: {}", this->getLoggingPeerIp(), key);
|
||||
@@ -758,22 +763,24 @@ command_result QueryClient::handleCommandHostInfo(Command &) {
|
||||
res["virtualservers_total_channels_online"] = vsReport.onlineChannels;
|
||||
|
||||
|
||||
auto stats = serverInstance->getStatistics()->statistics();
|
||||
res["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
|
||||
res["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
|
||||
res["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
|
||||
res["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
|
||||
auto total_stats = serverInstance->getStatistics()->total_stats();
|
||||
res["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), 0U);
|
||||
res["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), 0U);
|
||||
res["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), 0U);
|
||||
res["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), 0U);
|
||||
|
||||
auto report = serverInstance->getStatistics()->dataReport();
|
||||
res["connection_bandwidth_sent_last_second_total"] = report.send_second;
|
||||
res["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
|
||||
res["connection_bandwidth_received_last_second_total"] = report.recv_second;
|
||||
res["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
|
||||
auto report_second = serverInstance->getStatistics()->second_stats();
|
||||
auto report_minute = serverInstance->getStatistics()->minute_stats();
|
||||
res["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
|
||||
res["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), 0U);
|
||||
res["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
|
||||
res["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), 0U);
|
||||
|
||||
res["connection_filetransfer_bandwidth_sent"] = report.file_send;
|
||||
res["connection_filetransfer_bandwidth_received"] = report.file_recv;
|
||||
res["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
|
||||
res["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
|
||||
|
||||
res["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
|
||||
res["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
|
||||
res["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
|
||||
res["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
|
||||
|
||||
this->sendCommand(res);
|
||||
return command_result{error::ok};
|
||||
@@ -867,6 +874,32 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
if(this->server) {
|
||||
return command_result{error::not_implemented};
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
//host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
|
||||
//port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
|
||||
} else {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
|
||||
//TODO: Create a server if no exists
|
||||
server_create_lock.unlock();
|
||||
|
||||
//TODO: Stop the server completely
|
||||
if(!serverInstance->getVoiceServerManager()->deploy_snapshot(error, 111, command)) {
|
||||
//TODO: Delete server is it was new
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_create, 1);
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
@@ -42,7 +42,7 @@ bool QueryClient::notifyServerUpdated(shared_ptr<ConnectedClient> ptr) {
|
||||
return ConnectedClient::notifyServerUpdated(ptr);
|
||||
}
|
||||
|
||||
bool QueryClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<std::shared_ptr<property::PropertyDescription>> &deque, bool lock_channel_tree) {
|
||||
bool QueryClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<const property::PropertyDescription*> &deque, bool lock_channel_tree) {
|
||||
CHK_EVENT(QEVENTGROUP_CLIENT_MISC, QEVENTSPECIFIER_CLIENT_MISC_UPDATE);
|
||||
return ConnectedClient::notifyClientUpdated(ptr, deque, lock_channel_tree);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Created by WolverinDEV on 06/04/2020.
|
||||
//
|
||||
|
||||
#include "PacketStatistics.h"
|
||||
|
||||
using namespace ts::server::client;
|
||||
|
||||
void PacketStatistics::received_packet(ts::protocol::PacketType type, uint32_t pid) {
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
switch (type) {
|
||||
case protocol::PacketType::VOICE:
|
||||
this->calculator_voice.packet_received(pid);
|
||||
return;
|
||||
case protocol::PacketType::VOICE_WHISPER:
|
||||
this->calculator_voice_whisper.packet_received(pid);
|
||||
return;
|
||||
|
||||
case protocol::PacketType::COMMAND:
|
||||
case protocol::PacketType::COMMAND_LOW:
|
||||
return;
|
||||
|
||||
case protocol::PacketType::ACK:
|
||||
this->calculator_ack.packet_received(pid);
|
||||
return;
|
||||
case protocol::PacketType::ACK_LOW:
|
||||
this->calculator_ack_low.packet_received(pid);
|
||||
return;
|
||||
case protocol::PacketType::PING:
|
||||
this->calculator_ping.packet_received(pid);
|
||||
return;
|
||||
|
||||
default:
|
||||
/* some invalid packet lul */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketStatistics::send_command(ts::protocol::PacketType type, uint32_t pid) {
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
if(type == protocol::PacketType::COMMAND)
|
||||
this->calculator_command.packet_send(pid);
|
||||
else if(type == protocol::PacketType::COMMAND_LOW)
|
||||
this->calculator_command_low.packet_send(pid);
|
||||
}
|
||||
|
||||
void PacketStatistics::received_acknowledge(ts::protocol::PacketType type, uint32_t pid) {
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
if(type == protocol::PacketType::ACK)
|
||||
this->calculator_command.ack_received(pid);
|
||||
else if(type == protocol::PacketType::ACK_LOW)
|
||||
this->calculator_command_low.ack_received(pid);
|
||||
}
|
||||
|
||||
PacketStatistics::PacketLossReport PacketStatistics::loss_report() const {
|
||||
PacketStatistics::PacketLossReport result{};
|
||||
|
||||
result.received_voice = this->calculator_voice.received_packets() + this->calculator_voice_whisper.received_packets();
|
||||
result.lost_voice = this->calculator_voice.lost_packets() + this->calculator_voice_whisper.lost_packets();
|
||||
|
||||
result.received_keep_alive = this->calculator_ping.received_packets();
|
||||
result.lost_keep_alive = this->calculator_ping.lost_packets();
|
||||
|
||||
result.received_control = this->calculator_command.received_packets() + this->calculator_command_low.received_packets();
|
||||
result.lost_control = this->calculator_command.lost_packets() + this->calculator_command_low.lost_packets();
|
||||
//result.lost_control -= this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* subtract the lost acks (command received but ack got lost) */
|
||||
|
||||
result.received_control += this->calculator_ack.received_packets() + this->calculator_ack_low.received_packets();
|
||||
//result.lost_control += this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* this cancels out the line above */
|
||||
return result;
|
||||
}
|
||||
|
||||
void PacketStatistics::tick() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(now + std::chrono::seconds{15} > this->last_short) {
|
||||
this->last_short = now;
|
||||
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
this->calculator_command.short_stats();
|
||||
this->calculator_command_low.short_stats();
|
||||
|
||||
this->calculator_ack.short_stats();
|
||||
this->calculator_ack_low.short_stats();
|
||||
|
||||
this->calculator_voice.short_stats();
|
||||
this->calculator_voice_whisper.short_stats();
|
||||
|
||||
this->calculator_ping.short_stats();
|
||||
}
|
||||
}
|
||||
|
||||
void PacketStatistics::reset() {
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
this->calculator_command.reset();
|
||||
this->calculator_command_low.reset();
|
||||
|
||||
this->calculator_ack.reset();
|
||||
this->calculator_ack_low.reset();
|
||||
|
||||
this->calculator_voice.reset();
|
||||
this->calculator_voice_whisper.reset();
|
||||
|
||||
this->calculator_ping.reset();
|
||||
}
|
||||
|
||||
void PacketStatistics::reset_offsets() {
|
||||
std::lock_guard lock{this->data_mutex};
|
||||
this->calculator_command.reset_offsets();
|
||||
this->calculator_command_low.reset_offsets();
|
||||
|
||||
this->calculator_ack.reset_offsets();
|
||||
this->calculator_ack_low.reset_offsets();
|
||||
|
||||
this->calculator_voice.reset_offsets();
|
||||
this->calculator_voice_whisper.reset_offsets();
|
||||
|
||||
this->calculator_ping.reset_offsets();
|
||||
}
|
||||
|
||||
float PacketStatistics::current_packet_loss() const {
|
||||
auto report = this->loss_report();
|
||||
return report.total_loss();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/PacketLossCalculator.h>
|
||||
#include <protocol/Packet.h>
|
||||
#include <misc/spin_lock.h>
|
||||
|
||||
namespace ts::server::client {
|
||||
class PacketStatistics {
|
||||
public:
|
||||
struct PacketLossReport {
|
||||
uint32_t lost_voice{0};
|
||||
uint32_t lost_control{0};
|
||||
uint32_t lost_keep_alive{0};
|
||||
|
||||
uint32_t received_voice{0};
|
||||
uint32_t received_control{0};
|
||||
uint32_t received_keep_alive{0};
|
||||
|
||||
[[nodiscard]] inline float voice_loss() const {
|
||||
const auto total_packets = this->received_voice + this->lost_voice;
|
||||
if(total_packets == 0) return 0;
|
||||
return this->lost_voice / (float) total_packets;
|
||||
}
|
||||
[[nodiscard]] inline float control_loss() const {
|
||||
const auto total_packets = this->received_control + this->lost_control;
|
||||
//if(total_packets == 0) return 0; /* not possible so remove this to speed it up */
|
||||
return this->lost_control / (float) total_packets;
|
||||
}
|
||||
[[nodiscard]] inline float keep_alive_loss() const {
|
||||
const auto total_packets = this->received_keep_alive + this->lost_keep_alive;
|
||||
if(total_packets == 0) return 0;
|
||||
return this->lost_keep_alive / (float) total_packets;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline float total_loss() const {
|
||||
const auto total_lost = this->lost_voice + this->lost_control + this->lost_keep_alive;
|
||||
const auto total_received = this->received_control + this->received_voice + this->received_keep_alive;
|
||||
//if(total_received + total_lost == 0) return 0; /* not possible to speed this up */
|
||||
return total_lost / (float) (total_lost + total_received);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] PacketLossReport loss_report() const;
|
||||
[[nodiscard]] float current_packet_loss() const;
|
||||
|
||||
void send_command(protocol::PacketType /* type */, uint32_t /* packet id */);
|
||||
void received_acknowledge(protocol::PacketType /* type */, uint32_t /* packet id */);
|
||||
|
||||
void received_packet(protocol::PacketType /* type */, uint32_t /* packet id */);
|
||||
void tick();
|
||||
void reset();
|
||||
void reset_offsets();
|
||||
private:
|
||||
std::chrono::system_clock::time_point last_short{};
|
||||
|
||||
spin_lock data_mutex{};
|
||||
protocol::UnorderedPacketLossCalculator calculator_voice_whisper{};
|
||||
protocol::UnorderedPacketLossCalculator calculator_voice{};
|
||||
|
||||
protocol::UnorderedPacketLossCalculator calculator_ack_low{};
|
||||
protocol::UnorderedPacketLossCalculator calculator_ack{};
|
||||
|
||||
protocol::UnorderedPacketLossCalculator calculator_ping{};
|
||||
|
||||
protocol::CommandPacketLossCalculator calculator_command{};
|
||||
protocol::CommandPacketLossCalculator calculator_command_low{};
|
||||
};
|
||||
}
|
||||
@@ -1,50 +1,51 @@
|
||||
#include "PrecomputedPuzzles.h"
|
||||
#include "../../Configuration.h"
|
||||
#include "../ConnectedClient.h"
|
||||
#include "./PrecomputedPuzzles.h"
|
||||
#include "src/Configuration.h"
|
||||
#include <tomcrypt.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
using namespace ts::protocol;
|
||||
using namespace ts::server::udp;
|
||||
|
||||
PuzzleManager::PuzzleManager() {}
|
||||
PuzzleManager::~PuzzleManager() {}
|
||||
PuzzleManager::PuzzleManager() = default;
|
||||
PuzzleManager::~PuzzleManager() = default;
|
||||
|
||||
size_t PuzzleManager::precomputedPuzzleCount() { return this->cached.size(); }
|
||||
|
||||
bool PuzzleManager::precomputePuzzles(size_t limit) {
|
||||
while(precomputedPuzzleCount() < limit) generatePuzzle();
|
||||
return true;
|
||||
size_t PuzzleManager::precomputed_puzzle_count() {
|
||||
std::lock_guard lock{this->cache_lock};
|
||||
return this->cached_puzzles.size();
|
||||
}
|
||||
|
||||
std::shared_ptr<Puzzle> PuzzleManager::nextPuzzle() {
|
||||
this->indexLock.lock();
|
||||
size_t index = this->cacheIndex++ % this->cached.size();
|
||||
this->indexLock.unlock();
|
||||
return this->cached[index];
|
||||
bool PuzzleManager::precompute_puzzles(size_t amount) {
|
||||
std::random_device rd{};
|
||||
std::mt19937 mt{rd()};
|
||||
|
||||
amount = 5;
|
||||
while(this->precomputed_puzzle_count() < amount)
|
||||
this->generate_puzzle(mt);
|
||||
return this->precomputed_puzzle_count() > 0;
|
||||
}
|
||||
|
||||
inline void rndNum(mp_int *result, int byteLength){
|
||||
uint8_t buffer[byteLength];
|
||||
std::shared_ptr<Puzzle> PuzzleManager::next_puzzle() {
|
||||
std::lock_guard lock{this->cache_lock};
|
||||
return this->cached_puzzles[this->cache_index++ % this->cached_puzzles.size()];
|
||||
}
|
||||
|
||||
for(int index = 0; index < byteLength; index++) {
|
||||
int rnd = rand();
|
||||
uint8_t urnd = static_cast<uint8_t>(rnd & 0xFF);
|
||||
buffer[index] = urnd; //TODO more secure!
|
||||
inline void random_number(std::mt19937& generator, mp_int *result, int length){
|
||||
std::uniform_int_distribution<uint8_t> dist{};
|
||||
|
||||
}
|
||||
uint8_t buffer[length];
|
||||
for(auto& byte : buffer)
|
||||
byte = dist(generator);
|
||||
|
||||
mp_zero(result);
|
||||
mp_read_unsigned_bin(result, buffer, byteLength);
|
||||
mp_read_unsigned_bin(result, buffer, length);
|
||||
}
|
||||
|
||||
inline bool solvePuzzle(Puzzle *puzzle){
|
||||
inline bool solve_puzzle(Puzzle *puzzle) {
|
||||
mp_int exp{};
|
||||
mp_init(&exp);
|
||||
mp_2expt(&exp, puzzle->level);
|
||||
|
||||
|
||||
if (mp_exptmod(&puzzle->x, &exp, &puzzle->n, &puzzle->result) != CRYPT_OK) { //Sometimes it fails (unknow why :D)
|
||||
if (mp_exptmod(&puzzle->x, &exp, &puzzle->n, &puzzle->result) != CRYPT_OK) { //Sometimes it fails (unknown why :D)
|
||||
mp_clear(&exp);
|
||||
return false;
|
||||
}
|
||||
@@ -66,17 +67,17 @@ inline bool write_bin_data(mp_int& data, uint8_t* result, size_t length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PuzzleManager::generatePuzzle() {
|
||||
void PuzzleManager::generate_puzzle(std::mt19937& random_generator) {
|
||||
auto puzzle = new Puzzle{};
|
||||
|
||||
puzzle->level = ts::config::voice::RsaPuzzleLevel;
|
||||
mp_init_multi(&puzzle->x, &puzzle->n, &puzzle->result, nullptr);
|
||||
|
||||
generate_new:
|
||||
rndNum(&puzzle->x, 64);
|
||||
rndNum(&puzzle->n, 64);
|
||||
puzzle->level = ts::config::voice::RsaPuzzleLevel;
|
||||
random_number(random_generator, &puzzle->x, 64);
|
||||
random_number(random_generator, &puzzle->n, 64);
|
||||
|
||||
if(!solvePuzzle(puzzle))
|
||||
if(!solve_puzzle(puzzle))
|
||||
goto generate_new;
|
||||
|
||||
auto valid_x = mp_unsigned_bin_size(&puzzle->x) <= 64;
|
||||
@@ -94,7 +95,7 @@ void PuzzleManager::generatePuzzle() {
|
||||
if(!write_bin_data(puzzle->result, puzzle->data_result, 64))
|
||||
goto generate_new;
|
||||
|
||||
this->cached.push_back(shared_ptr<Puzzle>(puzzle, [](Puzzle* elm){
|
||||
this->cached_puzzles.push_back(shared_ptr<Puzzle>(puzzle, [](Puzzle* elm){
|
||||
mp_clear_multi(&elm->n, &elm->x, &elm->result, nullptr);
|
||||
delete elm;
|
||||
}));
|
||||
|
||||
@@ -1,44 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <tommath.h>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <misc/spin_lock.h>
|
||||
#include <random>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class ConnectedClient;
|
||||
}
|
||||
namespace ts::server::udp {
|
||||
struct Puzzle {
|
||||
mp_int x;
|
||||
mp_int n;
|
||||
int level;
|
||||
|
||||
namespace protocol {
|
||||
struct Puzzle {
|
||||
mp_int x;
|
||||
mp_int n;
|
||||
int level;
|
||||
mp_int result;
|
||||
|
||||
mp_int result;
|
||||
uint8_t data_x[64];
|
||||
uint8_t data_n[64];
|
||||
uint8_t data_result[64];
|
||||
};
|
||||
|
||||
uint8_t data_x[64];
|
||||
uint8_t data_n[64];
|
||||
uint8_t data_result[64];
|
||||
};
|
||||
class PuzzleManager {
|
||||
public:
|
||||
PuzzleManager();
|
||||
~PuzzleManager();
|
||||
class PuzzleManager {
|
||||
public:
|
||||
PuzzleManager();
|
||||
~PuzzleManager();
|
||||
|
||||
bool precomputePuzzles(size_t limit);
|
||||
[[nodiscard]] bool precompute_puzzles(size_t amount);
|
||||
|
||||
size_t precomputedPuzzleCount();
|
||||
[[nodiscard]] size_t precomputed_puzzle_count();
|
||||
|
||||
std::shared_ptr<Puzzle> nextPuzzle();
|
||||
private:
|
||||
void generatePuzzle();
|
||||
[[nodiscard]] std::shared_ptr<Puzzle> next_puzzle();
|
||||
private:
|
||||
void generate_puzzle(std::mt19937&);
|
||||
|
||||
threads::Mutex indexLock;
|
||||
size_t cacheIndex = 0;
|
||||
|
||||
std::deque<std::shared_ptr<Puzzle>> cached;
|
||||
};
|
||||
}
|
||||
size_t cache_index{0};
|
||||
spin_lock cache_lock{};
|
||||
std::vector<std::shared_ptr<Puzzle>> cached_puzzles{};
|
||||
};
|
||||
}
|
||||
@@ -108,6 +108,8 @@ void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
|
||||
} else
|
||||
this->sendPingRequest();
|
||||
}
|
||||
|
||||
this->connection->packet_statistics().tick();
|
||||
} else if(this->state == ConnectionState::INIT_LOW || this->state == ConnectionState::INIT_HIGH) {
|
||||
if(this->last_packet_handshake.time_since_epoch().count() != 0) {
|
||||
if(time - this->last_packet_handshake > seconds(5)) {
|
||||
@@ -309,4 +311,12 @@ void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &voice_buff
|
||||
|
||||
memcpy(packet->data().data_ptr<void>(), voice_buffer.data_ptr<void>(), voice_buffer.length());
|
||||
this->connection->sendPacket(packet, false, false);
|
||||
}
|
||||
|
||||
float VoiceClient::current_ping_deviation() {
|
||||
return this->connection->getAcknowledgeManager().current_rttvar();
|
||||
}
|
||||
|
||||
float VoiceClient::current_packet_loss() const {
|
||||
return this->connection->packet_statistics().current_packet_loss();
|
||||
}
|
||||
@@ -63,7 +63,11 @@ namespace ts {
|
||||
|
||||
connection::VoiceClientConnection* getConnection(){ return connection; }
|
||||
std::shared_ptr<VoiceServer> getVoiceServer(){ return voice_server; }
|
||||
std::chrono::milliseconds calculatePing(){ return ping; }
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds current_ping(){ return ping; }
|
||||
[[nodiscard]] float current_ping_deviation();
|
||||
|
||||
[[nodiscard]] float current_packet_loss() const;
|
||||
private:
|
||||
connection::VoiceClientConnection* connection;
|
||||
|
||||
@@ -120,6 +124,7 @@ namespace ts {
|
||||
bool client_init = false;
|
||||
bool new_protocol = false;
|
||||
bool protocol_encrypted = false;
|
||||
bool is_teaspeak_client = false;
|
||||
|
||||
uint32_t client_time = 0;
|
||||
std::string alpha;
|
||||
|
||||
@@ -78,7 +78,6 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ClientPacketParser packet_parser{buffer};
|
||||
if(!packet_parser.valid()) {
|
||||
logTrace(this->client->getServerId(), "{} Received invalid packet. Dropping.", CLIENT_STR_LOG_PREFIX_(this->client));
|
||||
@@ -87,6 +86,15 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
|
||||
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
||||
packet_parser.set_estimated_generation(this->incoming_generation_estimators[packet_parser.type()].visit_packet(packet_parser.packet_id()));
|
||||
|
||||
|
||||
#ifndef CONNECTION_NO_STATISTICS
|
||||
if(this->client) {
|
||||
auto stats = this->client->connectionStatistics;
|
||||
stats->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length() + 96); /* 96 for the UDP packet overhead */
|
||||
}
|
||||
this->packet_statistics().received_packet((protocol::PacketType) packet_parser.type(), packet_parser.full_packet_id());
|
||||
#endif
|
||||
|
||||
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
|
||||
/* pretest if the packet is worth the effort of decoding it */
|
||||
if(is_command) {
|
||||
@@ -168,11 +176,6 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef CONNECTION_NO_STATISTICS
|
||||
if(this->client && this->client->getServer())
|
||||
this->client->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length());
|
||||
#endif
|
||||
|
||||
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
|
||||
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
|
||||
#endif
|
||||
@@ -252,8 +255,10 @@ void VoiceClientConnection::execute_handle_command_packets(const std::chrono::sy
|
||||
buffer_execute_lock.unlock();
|
||||
|
||||
auto voice_server = this->client->voice_server;
|
||||
if(voice_server && reexecute_handle)
|
||||
if(voice_server && (reexecute_handle || this->should_reassembled_reschedule)) {
|
||||
should_reassembled_reschedule = false;
|
||||
this->client->voice_server->schedule_command_handling(this->client);
|
||||
}
|
||||
}
|
||||
|
||||
/* buffer_execute_lock: lock for in order execution */
|
||||
@@ -267,13 +272,19 @@ bool VoiceClientConnection::next_reassembled_command(unique_lock<std::recursive_
|
||||
|
||||
/* handle commands before command low packets */
|
||||
for(auto& buf : this->_command_fragment_buffers) {
|
||||
unique_lock ring_lock(buf.buffer_lock, try_to_lock);
|
||||
if(!ring_lock.owns_lock()) continue;
|
||||
unique_lock ring_lock(buf.buffer_lock, try_to_lock); //Perm lock the buffer else, may command wount get handeled. Because we've more left, but say we waven't
|
||||
if(!ring_lock.owns_lock()) {
|
||||
this->should_reassembled_reschedule = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(buf.front_set()) {
|
||||
if(!buffer) { /* lets still test for reexecute */
|
||||
buffer_execute_lock = unique_lock(buf.execute_lock, try_to_lock);
|
||||
if(!buffer_execute_lock.owns_lock()) continue;
|
||||
if(!buffer_execute_lock.owns_lock()) {
|
||||
this->should_reassembled_reschedule = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer_lock = move(ring_lock);
|
||||
buffer = &buf;
|
||||
@@ -477,7 +488,11 @@ bool VoiceClientConnection::prepare_packet_for_write(vector<pipes::buffer> &resu
|
||||
for(const auto& fragment : fragments) {
|
||||
if(!fragment->memory_state.id_branded)
|
||||
fragment->applyPacketId(this->packet_id_manager);
|
||||
|
||||
if(fragment->type().type() == protocol::PacketType::COMMAND_LOW || fragment->type().type() == protocol::PacketType::COMMAND)
|
||||
this->packet_statistics().send_command(fragment->type().type(), fragment->packetId() | fragment->generationId() << 16U);
|
||||
}
|
||||
|
||||
work_lock.unlock(); /* the rest could be unordered */
|
||||
|
||||
|
||||
@@ -509,8 +524,10 @@ bool VoiceClientConnection::prepare_packet_for_write(vector<pipes::buffer> &resu
|
||||
}
|
||||
|
||||
#ifndef CONNECTION_NO_STATISTICS
|
||||
if(statistics)
|
||||
statistics->logOutgoingPacket(*fragment);
|
||||
if(statistics) {
|
||||
auto category = stats::ConnectionStatistics::category::from_type(fragment->type());
|
||||
statistics->logOutgoingPacket(category, fragment->length() + 96); /* 96 for the UDP packet overhead */
|
||||
}
|
||||
#endif
|
||||
this->acknowledge_handler.process_packet(*fragment);
|
||||
result.push_back(fragment->buffer());
|
||||
@@ -576,7 +593,7 @@ int VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
|
||||
if(this->client->state == DISCONNECTED)
|
||||
return 2;
|
||||
|
||||
lock_guard write_queue_lock(this->write_queue_lock);
|
||||
lock_guard wqlock{this->write_queue_lock};
|
||||
size_t size = this->write_queue.size();
|
||||
if(size == 0)
|
||||
return 2;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "VoiceClient.h"
|
||||
#include "protocol/AcknowledgeManager.h"
|
||||
#include <protocol/generation.h>
|
||||
#include "./PacketStatistics.h"
|
||||
|
||||
//#define LOG_ACK_SYSTEM
|
||||
#ifdef LOG_ACK_SYSTEM
|
||||
@@ -82,10 +83,14 @@ namespace ts {
|
||||
bool wait_empty_write_and_prepare_queue(std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
|
||||
|
||||
protocol::PacketIdManager& getPacketIdManager() { return this->packet_id_manager; }
|
||||
AcknowledgeManager& getAcknowledgeManager() { return this->acknowledge_handler; }
|
||||
inline auto& get_incoming_generation_estimators() { return this->incoming_generation_estimators; }
|
||||
void reset();
|
||||
|
||||
void force_insert_command(const pipes::buffer_view& /* payload */);
|
||||
void register_initiv_packet();
|
||||
|
||||
[[nodiscard]] inline auto& packet_statistics() { return this->packet_statistics_; }
|
||||
//buffer::SortedBufferQueue<protocol::ClientPacket>** getReadQueue() { return this->readTypedQueue; }
|
||||
protected:
|
||||
void handle_incoming_datagram(const pipes::buffer_view &buffer);
|
||||
@@ -100,6 +105,8 @@ namespace ts {
|
||||
CompressionHandler compress_handler;
|
||||
AcknowledgeManager acknowledge_handler;
|
||||
|
||||
std::atomic_bool should_reassembled_reschedule; /* this get checked as soon the command handle lock has been released so trylock will succeed */
|
||||
|
||||
//Handle stuff
|
||||
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
|
||||
bool next_reassembled_command(std::unique_lock<std::recursive_timed_mutex> &buffer_execute_lock /* packet channel execute lock */, pipes::buffer & /* buffer*/, uint16_t& /* packet id */);
|
||||
@@ -172,6 +179,7 @@ namespace ts {
|
||||
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
|
||||
}
|
||||
|
||||
server::client::PacketStatistics packet_statistics_{};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
|
||||
|
||||
this->connection->reset();
|
||||
this->connection->register_initiv_packet();
|
||||
this->connection->packet_statistics().reset_offsets();
|
||||
this->crypto.protocol_encrypted = false;
|
||||
|
||||
bool use_teaspeak = command.hasParm("teaspeak");
|
||||
|
||||
@@ -81,7 +81,12 @@ void VoiceClient::handlePacketVoice(const protocol::ClientPacketParser& packet)
|
||||
}
|
||||
|
||||
void VoiceClient::handlePacketAck(const protocol::ClientPacketParser& packet) {
|
||||
if(packet.payload_length() < 2) return;
|
||||
uint16_t target_id{be2le16(packet.payload().data_ptr<char>())};
|
||||
|
||||
this->connection->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (packet.estimated_generation() << 16U));
|
||||
|
||||
string error{};
|
||||
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
|
||||
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), target_id, error))
|
||||
debugMessage(this->getServerId(), "{} Failed to handle acknowledge: {}", CLIENT_STR_LOG_PREFIX, error);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <pipes/rtc/PeerConnection.h>
|
||||
#include <pipes/rtc/AudioStream.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <dlfcn.h>
|
||||
#include "WebClient.h"
|
||||
#include "VoiceBridge.h"
|
||||
|
||||
@@ -11,7 +10,7 @@ using namespace ts;
|
||||
using namespace ts::server;
|
||||
using namespace ts::web;
|
||||
|
||||
void log(pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
|
||||
void VoiceBridge::callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
|
||||
auto max_length = 1024 * 8;
|
||||
char buffer[max_length];
|
||||
|
||||
@@ -20,7 +19,49 @@ void log(pipes::Logger::LogLevel level, const std::string& name, const std::stri
|
||||
max_length = vsnprintf(buffer, max_length, message.c_str(), args);
|
||||
va_end(args);
|
||||
|
||||
debugMessage(LOG_GENERAL, "[WebRTC][{}][{}] {}", level, name, string(buffer));
|
||||
auto bridge = (VoiceBridge*) ptr;
|
||||
debugMessage(LOG_GENERAL, "{}[WebRTC][{}][{}] {}", CLIENT_STR_LOG_PREFIX_(bridge->owner()), level, name, string(buffer));
|
||||
}
|
||||
|
||||
namespace gioloop {
|
||||
void* main_loop_;
|
||||
|
||||
void*(*g_main_loop_new)(void* /* context */, bool /* is true */);
|
||||
void(*g_main_loop_run)(void* /* loop */);
|
||||
void(*g_main_loop_unref)(void* /* loop */);
|
||||
void*(*g_main_loop_ref)(void* /* loop */);
|
||||
|
||||
bool initialized{false};
|
||||
void initialize() {
|
||||
if(initialized) return;
|
||||
initialized = true;
|
||||
|
||||
g_main_loop_new = (decltype(g_main_loop_new)) dlsym(nullptr, "g_main_loop_new");
|
||||
g_main_loop_run = (decltype(g_main_loop_run)) dlsym(nullptr, "g_main_loop_run");
|
||||
g_main_loop_ref = (decltype(g_main_loop_ref)) dlsym(nullptr, "g_main_loop_ref");
|
||||
g_main_loop_unref = (decltype(g_main_loop_unref)) dlsym(nullptr, "g_main_loop_unref");
|
||||
|
||||
if(!g_main_loop_run || !g_main_loop_new || !g_main_loop_ref || !g_main_loop_unref) {
|
||||
logWarning(LOG_INSTANCE, "Missing g_main_loop_new, g_main_loop_run, g_main_loop_ref or g_main_loop_unref functions. Could not spawn main loop.");
|
||||
g_main_loop_run = nullptr;
|
||||
g_main_loop_new = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
main_loop_ = g_main_loop_new(nullptr, false);
|
||||
if(!main_loop_) {
|
||||
logError(LOG_INSTANCE, "Failed to spawn new event loop for the web client.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::thread([]{
|
||||
g_main_loop_run(main_loop_);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
std::shared_ptr<GMainLoop> loop() {
|
||||
return std::shared_ptr<GMainLoop>{(GMainLoop*) g_main_loop_ref(main_loop_), g_main_loop_unref};
|
||||
}
|
||||
}
|
||||
|
||||
VoiceBridge::VoiceBridge(const shared_ptr<WebClient>& owner) : _owner(owner) {
|
||||
@@ -48,14 +89,16 @@ VoiceBridge::VoiceBridge(const shared_ptr<WebClient>& owner) : _owner(owner) {
|
||||
config->nice_config->allow_ice_tcp = false;
|
||||
config->nice_config->use_upnp = config::web::enable_upnp;
|
||||
|
||||
//FIXME Use the internal thread or a shared worker
|
||||
/* Not creating the thread here because DataPipes has a better impl with a join
|
||||
gioloop::initialize();
|
||||
config->nice_config->main_loop = gioloop::loop();
|
||||
/*
|
||||
config->nice_config->main_loop = std::shared_ptr<GMainLoop>(g_main_loop_new(nullptr, false), g_main_loop_unref);
|
||||
std::thread(g_main_loop_run, config->nice_config->main_loop.get()).detach();
|
||||
*/
|
||||
|
||||
config->logger = make_shared<pipes::Logger>();
|
||||
config->logger->callback_log = log;
|
||||
config->logger->callback_log = VoiceBridge::callback_log;
|
||||
config->logger->callback_argument = this;
|
||||
//config->sctp.local_port = 5202; //Fire Fox don't support a different port :D
|
||||
|
||||
this->connection = make_unique<rtc::PeerConnection>(config);
|
||||
@@ -77,19 +120,17 @@ std::shared_ptr<server::WebClient> VoiceBridge::owner() {
|
||||
bool VoiceBridge::initialize(std::string &error) {
|
||||
if(!this->connection->initialize(error)) return false;
|
||||
|
||||
this->connection->callback_ice_candidate = [&](const rtc::IceCandidate& candidate, bool last_candidate) {
|
||||
auto function_callback = this->callback_ice_candidate;
|
||||
|
||||
if(function_callback)
|
||||
function_callback(candidate);
|
||||
if(last_candidate) {
|
||||
auto function_callback_end = this->callback_ice_candidate_finished;
|
||||
this->negotiation_possible = true;
|
||||
if(function_callback_end)
|
||||
function_callback_end();
|
||||
this->connection->callback_ice_candidate = [&](const rtc::IceCandidate& candidate) {
|
||||
if(!candidate.is_finished_candidate()) {
|
||||
if(auto callback{this->callback_ice_candidate}; callback)
|
||||
callback(candidate);
|
||||
} else {
|
||||
if(auto callback{this->callback_ice_candidate_finished}; callback)
|
||||
callback();
|
||||
}
|
||||
};
|
||||
this->connection->callback_new_stream = [&](const std::shared_ptr<rtc::Stream> &channel) { this->handle_media_stream(channel); }; //bind(&VoiceBridge::handle_media_stream, this, placeholders::_1); => crash
|
||||
|
||||
this->connection->callback_new_stream = [&](const std::shared_ptr<rtc::Channel> &channel) { this->handle_media_stream(channel); }; //bind(&VoiceBridge::handle_media_stream, this, placeholders::_1); => crash
|
||||
this->connection->callback_setup_fail = [&](rtc::PeerConnection::ConnectionComponent comp, const std::string& reason) {
|
||||
debugMessage(this->server_id(), "{} WebRTC setup failed! Component {} ({})", CLIENT_STR_LOG_PREFIX_(this->owner()), comp, reason);
|
||||
if(this->callback_failed)
|
||||
@@ -109,45 +150,45 @@ int VoiceBridge::apply_ice(const std::deque<std::shared_ptr<rtc::IceCandidate>>&
|
||||
}
|
||||
|
||||
void VoiceBridge::remote_ice_finished() {
|
||||
if(negotiation_possible) {
|
||||
this->connection->execute_negotiation();
|
||||
negotiation_required = false;
|
||||
} else {
|
||||
negotiation_required = true;
|
||||
}
|
||||
this->connection->remote_candidates_finished();
|
||||
}
|
||||
|
||||
std::string VoiceBridge::generate_answer() {
|
||||
return this->connection->generate_answer(true);
|
||||
return this->connection->generate_answer(false);
|
||||
}
|
||||
|
||||
void VoiceBridge::execute_tick() {
|
||||
if(!this->_voice_channel) {
|
||||
if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds(10) < chrono::system_clock::now()) {
|
||||
if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds{20} < chrono::system_clock::now()) {
|
||||
this->offer_timestamp = chrono::system_clock::time_point();
|
||||
this->connection->callback_setup_fail(rtc::PeerConnection::ConnectionComponent::BASE, "setup timeout");
|
||||
}
|
||||
}
|
||||
if(this->negotiation_required && this->negotiation_possible) {
|
||||
this->connection->execute_negotiation();
|
||||
negotiation_required = false;
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Stream> &undefined_stream) {
|
||||
void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Channel> &undefined_stream) {
|
||||
if(undefined_stream->type() == rtc::CHANTYPE_APPLICATION) {
|
||||
auto stream = dynamic_pointer_cast<rtc::ApplicationStream>(undefined_stream);
|
||||
auto stream = dynamic_pointer_cast<rtc::ApplicationChannel>(undefined_stream);
|
||||
if(!stream) return;
|
||||
|
||||
stream->callback_datachannel_new = [&](const std::shared_ptr<rtc::DataChannel> &channel) { this->handle_data_channel(channel); }; //bind(&VoiceBridge::handle_data_channel, this, placeholders::_1); => may crash?
|
||||
} else if(undefined_stream->type() == rtc::CHANTYPE_AUDIO) {
|
||||
auto stream = dynamic_pointer_cast<rtc::AudioStream>(undefined_stream);
|
||||
auto stream = dynamic_pointer_cast<rtc::AudioChannel>(undefined_stream);
|
||||
if(!stream) return;
|
||||
this->_audio_channel = stream;
|
||||
|
||||
for(const auto& ex : stream->list_extensions()) {
|
||||
debugMessage(0, "{} | {}", ex->name, ex->id);
|
||||
}
|
||||
|
||||
stream->register_local_extension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
|
||||
//bind(&VoiceBridge::handle_audio_data, this, placeholders::_1, placeholders::_2, placeholders::_3); => may crash?
|
||||
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::AudioChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); };
|
||||
for(const auto& codec : stream->list_codecs()) {
|
||||
if(codec->type == rtc::codec::Codec::OPUS) {
|
||||
codec->accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); };
|
||||
} else {
|
||||
logError(this->server_id(), "Got offer for unknown channel of type {}", undefined_stream->type());
|
||||
}
|
||||
@@ -174,42 +215,8 @@ void VoiceBridge::handle_data_channel(const std::shared_ptr<rtc::DataChannel> &c
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
ssize_t sample_count = 960;
|
||||
ssize_t sample_length = sample_count * 2;
|
||||
opus_int16 samples[sample_length];
|
||||
|
||||
if(!this->decoder) {
|
||||
int error;
|
||||
this->decoder = opus_decoder_create(48000, 2, &error);
|
||||
assert(error == 0 && this->decoder);
|
||||
}
|
||||
if(!this->encoder) {
|
||||
int error;
|
||||
this->encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_AUDIO, &error);
|
||||
assert(error == 0 && this->encoder);
|
||||
}
|
||||
|
||||
{
|
||||
sample_count = opus_decode(this->decoder, (u_char*) &data.data()[payload_offset], data.length() - payload_offset, samples, sample_count, 0);
|
||||
if(sample_count < 0) {
|
||||
logError(this->server_id(), "Could not decode opus!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string target_buffer(512, '\0'); //Way too big!
|
||||
le2be16(this->voice.packet_id++, (char*) target_buffer.data());
|
||||
target_buffer[2] = 5;
|
||||
|
||||
auto encoded = opus_encode(this->encoder, samples, sample_count, (u_char*) &target_buffer[3], 512 - 3);
|
||||
if(encoded < 0 ){
|
||||
logError(this->server_id(), "Could not encode opus!");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::AudioChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
if(channel->codec->type != rtc::codec::TypedAudio::OPUS) {
|
||||
void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
|
||||
if(channel->codec->type != rtc::codec::Codec::OPUS) {
|
||||
debugMessage(this->server_id(), "{} Got unknown codec ({})!", CLIENT_STR_LOG_PREFIX_(this->owner()), channel->codec->type);
|
||||
return;
|
||||
}
|
||||
@@ -217,7 +224,7 @@ void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::AudioChannel> &ch
|
||||
auto ac = _audio_channel.lock();
|
||||
if(!ac) return;
|
||||
|
||||
for(const auto& ext : ac->list_extensions(0x02)) {
|
||||
for(const auto& ext : ac->list_extensions(rtc::direction::incoming)) {
|
||||
if(ext->name == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") {
|
||||
int level;
|
||||
if(rtc::protocol::rtp_header_extension_parse_audio_level(data, ext->id, &level) == 0) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <pipes/rtc/PeerConnection.h>
|
||||
#include <pipes/rtc/ApplicationStream.h>
|
||||
#include <pipes/rtc/AudioStream.h>
|
||||
#include <pipes/rtc/channels/ApplicationChannel.h>
|
||||
#include <pipes/rtc/channels/AudioChannel.h>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
@@ -36,26 +36,24 @@ namespace ts {
|
||||
|
||||
void execute_tick();
|
||||
private:
|
||||
static void callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...);
|
||||
|
||||
inline int server_id();
|
||||
inline std::shared_ptr<server::WebClient> owner();
|
||||
|
||||
void handle_media_stream(const std::shared_ptr<rtc::Stream>& /* stream */);
|
||||
void handle_media_stream(const std::shared_ptr<rtc::Channel>& /* stream */);
|
||||
void handle_data_channel(const std::shared_ptr<rtc::DataChannel> & /* channel */);
|
||||
void handle_audio_data(const std::shared_ptr<rtc::AudioChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
void handle_audio_data(const std::shared_ptr<rtc::MediaChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
|
||||
|
||||
std::weak_ptr<server::WebClient> _owner;
|
||||
std::chrono::system_clock::time_point offer_timestamp;
|
||||
std::unique_ptr<rtc::PeerConnection> connection;
|
||||
std::shared_ptr<rtc::DataChannel> _voice_channel;
|
||||
std::weak_ptr<rtc::AudioStream> _audio_channel;
|
||||
std::weak_ptr<rtc::AudioChannel> _audio_channel;
|
||||
struct {
|
||||
uint16_t packet_id = 0;
|
||||
bool muted = true;
|
||||
} voice;
|
||||
|
||||
bool negotiation_possible = false;
|
||||
bool negotiation_required = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -457,8 +457,11 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
unique_lock voice_bridge_lock(this->voice_bridge_lock);
|
||||
if(this->voice_bridge) {
|
||||
logError(this->server->getServerId(), "[{}] Tried to register a WebRTC channel twice!", CLIENT_STR_LOG_PREFIX_(this));
|
||||
//return;
|
||||
this->voice_bridge = nullptr;
|
||||
|
||||
std::thread([&, vb_ptr = std::move(this->voice_bridge), lock = this->ref()]() mutable {
|
||||
vb_ptr = nullptr;
|
||||
lock = nullptr;
|
||||
}).detach();
|
||||
}
|
||||
//TODO test if bridge already exists!
|
||||
this->voice_bridge = make_unique<web::VoiceBridge>(dynamic_pointer_cast<WebClient>(_this.lock())); //FIXME Add config
|
||||
@@ -476,8 +479,11 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
auto vb_ptr = &*this->voice_bridge; /* read only no lock needed */
|
||||
std::thread([&, vb_ptr, lock = this->ref()]{
|
||||
unique_lock vbl{this->voice_bridge_lock};
|
||||
if(&*this->voice_bridge == vb_ptr)
|
||||
this->voice_bridge.release();
|
||||
if(&*this->voice_bridge == vb_ptr) {
|
||||
auto bridge = std::exchange(this->voice_bridge, nullptr);
|
||||
vbl.unlock();
|
||||
bridge.reset();
|
||||
}
|
||||
}).detach();
|
||||
|
||||
Json::Value response;
|
||||
@@ -551,7 +557,7 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
this->sendJson(response);
|
||||
}
|
||||
} else if(subType == "ice") {
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
std::shared_lock read_voice_bridge_lock{this->voice_bridge_lock};
|
||||
if(!this->voice_bridge) {
|
||||
debugMessage(this->getServerId(), "[{}] Received remote ICE candidate without having a voice bridge! Dropping candidate.", CLIENT_STR_LOG_PREFIX);
|
||||
return;
|
||||
@@ -578,7 +584,7 @@ void WebClient::handleMessage(const std::string &message) {
|
||||
}
|
||||
}
|
||||
} else if(subType == "ice_finish") {
|
||||
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
|
||||
std::shared_lock read_voice_bridge_lock{this->voice_bridge_lock};
|
||||
if(!this->voice_bridge) {
|
||||
debugMessage(this->getServerId(), "[{}] Received remote ICE candidate without having a voice bridge! Dropping candidate.", CLIENT_STR_LOG_PREFIX);
|
||||
return;
|
||||
|
||||
@@ -79,7 +79,7 @@ void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &
|
||||
this->current_client->close_connection();
|
||||
}
|
||||
|
||||
this->current_client.release();
|
||||
this->current_client.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,9 @@ void LicenseService::handle_check_succeeded() {
|
||||
} else {
|
||||
logMessage(LOG_INSTANCE, strobf("Instance integrity has been validated.").string());
|
||||
}
|
||||
|
||||
if(!config::server::check_server_version_with_license())
|
||||
handle_check_fail(strobf("memory invalid").string());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -191,9 +194,10 @@ void LicenseService::handle_check_fail(const std::string &error) {
|
||||
this->timings.last_succeeded.time_since_epoch().count() == 0 ? this->timings.failed_count < 32 : /* About 12hours */
|
||||
this->timings.failed_count < 82 /* about 36 hours */
|
||||
);
|
||||
if(config::license->isPremium() && !soft_license_check) {
|
||||
const auto invalid_memory = !config::server::check_server_version_with_license();
|
||||
if(invalid_memory || (config::license->isPremium() && !soft_license_check)) {
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string());
|
||||
logCritical(LOG_INSTANCE, error);
|
||||
logCritical(LOG_INSTANCE, invalid_memory ? strobf("invalid memory").string() : error);
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
} else {
|
||||
@@ -225,7 +229,7 @@ void LicenseService::handle_dns_lookup_result(bool success, const std::variant<s
|
||||
this->request_state_ = request_state::connecting;
|
||||
|
||||
assert(!this->current_client);
|
||||
this->current_client = std::make_unique<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
|
||||
this->current_client = std::make_shared<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
|
||||
this->current_client->callback_connected = [&]{ this->handle_client_connected(); };
|
||||
this->current_client->callback_disconnected = [&](bool expected, const std::string& error) {
|
||||
this->handle_client_disconnected(error);
|
||||
@@ -305,11 +309,11 @@ void LicenseService::send_license_validate_request() {
|
||||
request.set_licensed(false);
|
||||
request.set_license_info(false);
|
||||
}
|
||||
request.set_memory_valid(config::server::check_server_version_with_license());
|
||||
request.mutable_info()->set_uname(this->license_request_data->info.uname);
|
||||
request.mutable_info()->set_version(this->license_request_data->info.version);
|
||||
request.mutable_info()->set_timestamp(this->license_request_data->info.timestamp.count());
|
||||
request.mutable_info()->set_unique_id(this->license_request_data->info.unique_id);
|
||||
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_SERVER_VALIDATION, request);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace ts::server::license {
|
||||
|
||||
std::recursive_timed_mutex request_lock{};
|
||||
request_state request_state_{request_state::empty};
|
||||
std::unique_ptr<::license::client::LicenseServerClient> current_client{nullptr};
|
||||
std::shared_ptr<::license::client::LicenseServerClient> current_client{nullptr};
|
||||
std::shared_ptr<InstanceLicenseInfo> license_request_data{nullptr};
|
||||
|
||||
std::condition_variable sync_request_cv;
|
||||
|
||||
@@ -222,7 +222,7 @@ namespace ts {
|
||||
|
||||
class ConversationManager {
|
||||
public:
|
||||
ConversationManager(const std::shared_ptr<VirtualServer>& /* server */);
|
||||
explicit ConversationManager(const std::shared_ptr<VirtualServer>& /* server */);
|
||||
virtual ~ConversationManager();
|
||||
|
||||
void initialize(const std::shared_ptr<ConversationManager>& _this);
|
||||
|
||||
@@ -44,8 +44,8 @@ if(!result && result.msg().find(ignore) == string::npos){
|
||||
|
||||
#define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")");
|
||||
|
||||
#define CURRENT_DATABASE_VERSION 11
|
||||
#define CURRENT_PERMISSION_VERSION 2
|
||||
#define CURRENT_DATABASE_VERSION 12
|
||||
#define CURRENT_PERMISSION_VERSION 3
|
||||
|
||||
#define CLIENT_UID_LENGTH "64"
|
||||
#define CLIENT_NAME_LENGTH "128"
|
||||
@@ -144,7 +144,7 @@ bool SqlDataManager::initialize(std::string& error) {
|
||||
//Advanced locked test
|
||||
{
|
||||
bool property_exists = false;
|
||||
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([](bool& flag, int, string*, string*) { flag = true; }, property_exists);
|
||||
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([&](int, string*, string*) { property_exists = true; });
|
||||
sql::result res;
|
||||
if(!property_exists) {
|
||||
res = sql::command(this->sql(), "INSERT INTO `general` (`key`, `value`) VALUES (:key, :value);", variable{":key", "lock_test"}, variable{":value", "UPDATE ME!"}).execute();
|
||||
@@ -411,6 +411,65 @@ ROLLBACK;
|
||||
CREATE_INDEX("conversations", "server_id");
|
||||
CREATE_INDEX2R("conversation_blocks", "server_id", "conversation_id");
|
||||
db_version(11);
|
||||
|
||||
case 11:
|
||||
/* update the group table */
|
||||
{
|
||||
result = sql::command(this->sql(), "CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128));").execute();
|
||||
if(!result) {
|
||||
error = "failed to create new groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "INSERT INTO `groups_v2`(`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;").execute();
|
||||
if(!result) {
|
||||
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
|
||||
error = "failed to insert data into the new groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "DROP TABLE `groups`;").execute();
|
||||
if(!result) {
|
||||
error = "failed to delete old groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "ALTER TABLE `groups_v2` RENAME TO groups;").execute();
|
||||
if(!result) {
|
||||
error = "failed to rename new groups table to the old groups table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
CREATE_INDEX2R("groups", "serverId", "groupId");
|
||||
CREATE_INDEX("groups", "serverId");
|
||||
result = sql::command(this->sql(), "ALTER TABLE `groups` ADD COLUMN `original_id` INTEGER DEFAULT 0;").execute();
|
||||
if(!result) {
|
||||
error = "Failed to alter groups table";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* update the client table */
|
||||
{
|
||||
result = sql::command(this->sql(), "CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `original_client_id` INTEGER DEFAULT 0, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));").execute();
|
||||
if(!result) {
|
||||
error = "failed to create new clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "INSERT INTO `clients_v2` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) SELECT `serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName` FROM `clients`;").execute();
|
||||
if(!result) {
|
||||
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
|
||||
error = "failed to insert data into the new clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "DROP TABLE `clients`;").execute();
|
||||
if(!result) {
|
||||
error = "failed to delete old clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
result = sql::command(this->sql(), "ALTER TABLE `clients_v2` RENAME TO clients;").execute();
|
||||
if(!result) {
|
||||
error = "failed to rename new clients table to the old clients table (" + result.fmtStr() + ")";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
db_version(12);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -443,14 +502,14 @@ bool SqlDataManager::update_permissions(std::string &error) {
|
||||
else
|
||||
query += "OR IGNORE ";
|
||||
query += "INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) ";
|
||||
query += string() + "SELECT DISTINCT `permissions`.`serverId`, 0, `groupId`, 0, "
|
||||
query += string() + "SELECT DISTINCT `permissions`.`serverId`, `permissions`.`type`, `groupId`, `permissions`.`channelId`, "
|
||||
+ "'" + permission + "', "
|
||||
+ to_string(value.has_value ? value.value : -2) + ", "
|
||||
+ to_string(granted.has_value ? granted.value : -2) + ", "
|
||||
+ to_string(skip) + ", "
|
||||
+ to_string(negate) + " FROM groups ";
|
||||
query += "INNER JOIN `permissions` ";
|
||||
query += "ON permissions.permId = 'i_group_auto_update_type' AND permissions.channelId = 0 AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = " + to_string(update_type);
|
||||
query += "ON permissions.permId = 'i_group_auto_update_type' AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = " + to_string(update_type);
|
||||
|
||||
logTrace(LOG_GENERAL, "Executing sql update: {}", query);
|
||||
auto result = sql::command(this->sql(), query).execute();
|
||||
@@ -547,6 +606,24 @@ bool SqlDataManager::update_permissions(std::string &error) {
|
||||
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_conversation_message_delete", {1, true}, false, false, {100, true}))
|
||||
return false;
|
||||
perm_version(2);
|
||||
|
||||
case 2:
|
||||
if(!auto_update(permission::update::SERVER_ADMIN, "b_client_query_create_own", {1, true}, false, false, {75, true}))
|
||||
return false;
|
||||
if(!auto_update(permission::update::QUERY_ADMIN, "b_client_query_create_own", {1, true}, false, false, {100, true}))
|
||||
return false;
|
||||
|
||||
/* for some reason some users haven't received these updates from last time */
|
||||
if(!auto_update(permission::update::SERVER_ADMIN, "i_playlist_song_move_power", {75, true}, false, false, {75, true}))
|
||||
return false;
|
||||
if(!auto_update(permission::update::QUERY_ADMIN, "i_playlist_song_move_power", {100, true}, false, false, {100, true}))
|
||||
return false;
|
||||
if(!auto_update(permission::update::SERVER_ADMIN, "i_playlist_song_needed_move_power", {0, false}, false, false, {75, true}))
|
||||
return false;
|
||||
if(!auto_update(permission::update::QUERY_ADMIN, "i_playlist_song_needed_move_power", {0, false}, false, false, {100, true}))
|
||||
return false;
|
||||
|
||||
perm_version(3);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -81,19 +81,22 @@ std::shared_ptr<server::MusicClient> MusicBotManager::createBot(ClientDbId owner
|
||||
musicBot->manager = this;
|
||||
musicBot->server = handle;
|
||||
DatabaseHelper::assignDatabaseId(handle->getSql(), handle->getServerId(), musicBot);
|
||||
{
|
||||
if(config::music::enabled) {
|
||||
lock_guard lock(this->music_bots_lock);
|
||||
this->music_bots.push_back(musicBot);
|
||||
}
|
||||
(LOG_SQL_CMD)(sql::command(handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:sid, :botId, :uid, :owner)",
|
||||
variable{":sid", handle->getServerId()}, variable{":botId", musicBot->getClientDatabaseId()}, variable{":uid", musicBot->getUid()}, variable{":owner", owner}).execute());
|
||||
musicBot->properties()[property::CLIENT_OWNER] = owner;
|
||||
handle->groups->enableCache(musicBot->getClientDatabaseId());
|
||||
musicBot->setDisplayName("Im a music bot!");
|
||||
musicBot->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
musicBot->properties()[property::CLIENT_CREATED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
musicBot->properties()[property::CLIENT_VERSION] = "TeaMusic";
|
||||
musicBot->properties()[property::CLIENT_PLATFORM] = "internal";
|
||||
|
||||
|
||||
if(!config::music::enabled) return nullptr;
|
||||
handle->groups->enableCache(musicBot->getClientDatabaseId());
|
||||
handle->registerClient(musicBot);
|
||||
|
||||
{
|
||||
@@ -283,7 +286,7 @@ void MusicBotManager::disconnectBots() {
|
||||
|
||||
void MusicBotManager::load_playlists() {
|
||||
if(!config::music::enabled) return;
|
||||
|
||||
|
||||
lock_guard playlist_lock(this->playlists_lock);
|
||||
auto sql_result = sql::command(this->ref_server()->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id", variable{":server_id", this->ref_server()->getServerId()}).query([&](int length, string* values, string* names){
|
||||
if(length != 1) return;
|
||||
|
||||
@@ -351,7 +351,7 @@ void Playlist::destroy_tree() {
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<PlaylistEntryInfo> > Playlist::list_songs() {
|
||||
std::deque<std::shared_ptr<PlaylistEntryInfo>> Playlist::list_songs() {
|
||||
unique_lock list_lock(this->playlist_lock);
|
||||
return this->_list_songs(list_lock);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::current_song(bool& aw
|
||||
if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as<SongId>() != id) /* should not happen */
|
||||
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id;
|
||||
|
||||
if(!entry || entry->metadata.requires_load()) return nullptr;
|
||||
if(!entry)
|
||||
return nullptr;
|
||||
|
||||
if(entry->metadata.is_loading()) {
|
||||
if(entry->metadata.load_begin + seconds(30) < system_clock::now()) {
|
||||
@@ -113,10 +114,10 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
|
||||
auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>();
|
||||
|
||||
unique_lock playlist_lock(this->playlist_lock);
|
||||
auto current_song = this->playlist_find(playlist_lock, this->currently_playing());
|
||||
auto old_song = this->playlist_find(playlist_lock, this->currently_playing());
|
||||
|
||||
if(replay_mode == ReplayMode::SINGLE_LOOPED) {
|
||||
if(current_song) return current_song->entry;
|
||||
if(old_song) return old_song->entry;
|
||||
if(this->playlist_head) return this->playlist_head->entry;
|
||||
|
||||
this->properties()[property::PLAYLIST_FLAG_FINISHED] = true;
|
||||
@@ -125,9 +126,9 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
|
||||
|
||||
std::shared_ptr<PlaylistEntryInfo> result;
|
||||
if(replay_mode == ReplayMode::LINEAR || replay_mode == ReplayMode::LINEAR_LOOPED) {
|
||||
if(current_song) {
|
||||
if(current_song->next_song)
|
||||
result = current_song->next_song->entry;
|
||||
if(old_song) {
|
||||
if(old_song->next_song)
|
||||
result = old_song->next_song->entry;
|
||||
else if(replay_mode == ReplayMode::LINEAR_LOOPED && this->playlist_head)
|
||||
result = this->playlist_head->entry;
|
||||
else {
|
||||
@@ -148,14 +149,14 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
|
||||
result = songs.front();
|
||||
} else {
|
||||
size_t index;
|
||||
while(songs[index = (rand() % songs.size())] == (!current_song ? nullptr : current_song->entry)) { }
|
||||
while(songs[index = (rand() % songs.size())] == (!old_song ? nullptr : old_song->entry)) { }
|
||||
|
||||
result = songs[index];
|
||||
}
|
||||
}
|
||||
playlist_lock.unlock();
|
||||
if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>()) {
|
||||
this->delete_song(current_song->entry->song_id);
|
||||
if(old_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>()) {
|
||||
this->delete_song(old_song->entry->song_id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#include "POWHandler.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "src/VirtualServerManager.h"
|
||||
#include "src/client/voice/VoiceClient.h"
|
||||
#include <misc/endianness.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/client/voice/VoiceClient.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -21,7 +19,7 @@ void POWHandler::execute_tick() {
|
||||
|
||||
lock_guard lock(this->pending_clients_lock);
|
||||
this->pending_clients.erase(remove_if(this->pending_clients.begin(), this->pending_clients.end(), [&, now](const shared_ptr<Client>& client) {
|
||||
if(now - client->last_packet > seconds(5)) {
|
||||
if(now - client->last_packet > std::chrono::seconds{5}) {
|
||||
#ifdef POW_ERROR
|
||||
if(client->state != LowHandshakeState::COMPLETED) { /* handshake succeeded */
|
||||
debugMessage(this->get_server_id(), "[POW] Dropping connection from {} (Timeout)", net::to_string(client->address));
|
||||
@@ -155,12 +153,6 @@ inline void generate_random(uint8_t *destination, size_t length) {
|
||||
*(destination++) = (uint8_t) rand();
|
||||
}
|
||||
|
||||
inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length) {
|
||||
destination += length;
|
||||
while(length-- > 0)
|
||||
*(--destination) = *(source++);
|
||||
}
|
||||
|
||||
void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
|
||||
if(buffer.length() != 21) {
|
||||
#ifdef POW_ERROR
|
||||
@@ -172,7 +164,7 @@ void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler:
|
||||
/* initialize data */
|
||||
if(client->server_control_data[0] == 0) {
|
||||
generate_random(client->server_control_data, 16);
|
||||
client->server_control_data[0] |= 1;
|
||||
client->server_control_data[0] |= 1U;
|
||||
}
|
||||
|
||||
/* parse values */
|
||||
@@ -183,7 +175,7 @@ void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler:
|
||||
uint8_t response_buffer[21];
|
||||
response_buffer[0] = LowHandshakeState::COOKIE_SET;
|
||||
memcpy(&response_buffer[1], client->server_control_data, 16);
|
||||
write_reversed(&response_buffer[17], client->client_control_data, 4);
|
||||
*(uint32_t*) &response_buffer[17] = htonl(*(uint32_t*) &client->client_control_data);
|
||||
|
||||
this->send_data(client, pipes::buffer_view{response_buffer, 21});
|
||||
}
|
||||
@@ -211,7 +203,7 @@ void POWHandler::handle_puzzle_get(const std::shared_ptr<ts::server::POWHandler:
|
||||
}
|
||||
|
||||
if(!client->rsa_challenge)
|
||||
client->rsa_challenge = serverInstance->getVoiceServerManager()->rsaPuzzles()->nextPuzzle();
|
||||
client->rsa_challenge = serverInstance->getVoiceServerManager()->rsaPuzzles()->next_puzzle();
|
||||
|
||||
/* send response */
|
||||
{
|
||||
@@ -262,6 +254,7 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandle
|
||||
#ifdef POW_ERROR
|
||||
debugMessage(this->get_server_id(), "[POW][{}][Puzzle] Received an invalid puzzle solution! Resetting client", net::to_string(client->address));
|
||||
#endif
|
||||
client->rsa_challenge.reset(); /* get another RSA challenge */
|
||||
this->reset_client(client);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "VoiceServer.h"
|
||||
#include "src/VirtualServer.h"
|
||||
|
||||
|
||||
namespace ts::server {
|
||||
class POWHandler {
|
||||
public:
|
||||
@@ -32,13 +33,13 @@ namespace ts::server {
|
||||
std::chrono::system_clock::time_point last_packet;
|
||||
LowHandshakeState state = LowHandshakeState::COOKIE_GET;
|
||||
|
||||
uint8_t client_control_data[4] = {0,0,0,0};
|
||||
uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
uint8_t client_control_data[4]{0};
|
||||
uint8_t server_control_data[16]{0};
|
||||
uint8_t server_data[100];
|
||||
|
||||
uint32_t client_version;
|
||||
|
||||
std::shared_ptr<protocol::Puzzle> rsa_challenge;
|
||||
std::shared_ptr<udp::Puzzle> rsa_challenge;
|
||||
};
|
||||
|
||||
explicit POWHandler(VoiceServer* /* server */);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <condition_variable>
|
||||
#include <pipes/buffer.h>
|
||||
#include <misc/spin_lock.h>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
|
||||
@@ -148,7 +148,7 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
|
||||
lock_guard lock(this->connectionLock);
|
||||
connections = this->activeConnections;
|
||||
}
|
||||
deque<pipes::buffer> buffers;
|
||||
deque<std::shared_ptr<connection::AcknowledgeManager::Entry>> buffers;
|
||||
string error;
|
||||
for(const auto& client : connections) {
|
||||
auto connection = client->getConnection();
|
||||
@@ -165,12 +165,16 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
|
||||
} else if(!buffers.empty()) {
|
||||
{
|
||||
lock_guard client_write_lock(connection->write_queue_lock);
|
||||
connection->write_queue.insert(connection->write_queue.end(), buffers.begin(), buffers.end());
|
||||
for(auto& buf : buffers)
|
||||
connection->write_queue.push_back(buf->buffer);
|
||||
}
|
||||
//logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size());
|
||||
buffers.clear();
|
||||
for(auto& entry : buffers)
|
||||
connection->packet_statistics().send_command((protocol::PacketType) entry->packet_type, entry->packet_id | entry->generation_id << 16U);
|
||||
//if(buffers.size() > 0)
|
||||
// logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size());
|
||||
connection->triggerWrite();
|
||||
}
|
||||
buffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
//
|
||||
// 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 "../InstanceHandler.h"
|
||||
#include <sql/insert.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 */
|
||||
{
|
||||
/* cleanup all old data */
|
||||
this->delete_server_in_db(server_id);
|
||||
|
||||
/* register clients */
|
||||
{
|
||||
//`original_client_id` INTEGER DEFAULT 0
|
||||
//CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));
|
||||
|
||||
sql::InsertQuery insert_general_query{"clients",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<ClientDbId>("original_client_id"),
|
||||
sql::Column<std::string>("clientUid"),
|
||||
sql::Column<int64_t>("firstConnect"),
|
||||
sql::Column<int64_t>("lastConnect"),
|
||||
|
||||
sql::Column<uint64_t>("connections"),
|
||||
sql::Column<std::string>("lastName")
|
||||
};
|
||||
|
||||
sql::InsertQuery insert_server_query{"clients",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<ClientDbId>("original_client_id"),
|
||||
sql::Column<std::string>("clientUid"),
|
||||
sql::Column<int64_t>("firstConnect"),
|
||||
sql::Column<int64_t>("lastConnect"),
|
||||
sql::Column<uint64_t>("connections"),
|
||||
sql::Column<std::string>("lastName")
|
||||
};
|
||||
|
||||
for(const auto& client : parsed_clients) {
|
||||
/*
|
||||
insert_general_query.add_entry(
|
||||
server_id,
|
||||
client.parsed_data.database_id,
|
||||
client.parsed_data.unique_id,
|
||||
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count(),
|
||||
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_last_connected.time_since_epoch()).count(),
|
||||
client.parsed_data.client_total_connections,
|
||||
client.parsed_data.nickname
|
||||
);
|
||||
*/
|
||||
insert_general_query.add_entry(
|
||||
server_id,
|
||||
client.parsed_data.database_id,
|
||||
client.parsed_data.unique_id,
|
||||
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count()
|
||||
);
|
||||
}
|
||||
|
||||
auto result = insert_general_query.execute(this->handle->getSql(), true);
|
||||
for(const auto& fail : result.failed_entries)
|
||||
logWarning(server_id, "Failed to insert client {} into the database: {}", parsed_clients[std::get<0>(fail)], std::get<1>(fail).fmtStr());
|
||||
|
||||
sql::command{this->handle->getSql(), "SELECT `original_client_id`,`cldbid` FROM `clients` WHERE `serverId` = :sid;"}
|
||||
.value(":serverId", server_id)
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
ClientDbId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||
return;
|
||||
}
|
||||
server_group_id_mapping[original_id] = new_id;
|
||||
});
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
{
|
||||
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
||||
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||
|
||||
for(auto& group : parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
|
||||
if(!result)
|
||||
logWarning(server_id, "Failed to insert server group \"{}\" into the database", group.parsed_data.name);
|
||||
}
|
||||
|
||||
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
GroupId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||
return;
|
||||
}
|
||||
server_group_id_mapping[original_id] = new_id;
|
||||
});
|
||||
}
|
||||
|
||||
/* channel groups */
|
||||
{
|
||||
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
|
||||
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
|
||||
|
||||
for(auto& group : parsed_server_groups) {
|
||||
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
|
||||
if(!result)
|
||||
logWarning(server_id, "Failed to insert channel group \"{}\" into the database", group.parsed_data.name);
|
||||
}
|
||||
|
||||
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
|
||||
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
GroupId original_id{0}, new_id{0};
|
||||
try {
|
||||
original_id = std::stoull(values[0]);
|
||||
new_id = std::stoull(values[1]);
|
||||
} catch (std::exception& ex) {
|
||||
logWarning(server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
|
||||
return;
|
||||
}
|
||||
channel_group_id_mapping[original_id] = new_id;
|
||||
});
|
||||
}
|
||||
|
||||
#define INSERT_PERMISSION_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
|
||||
/* client permissions */
|
||||
{
|
||||
sql::InsertQuery insert_query{"permissions",
|
||||
sql::Column<ServerId>("serverId"),
|
||||
sql::Column<permission::PermissionSqlType>("type"),
|
||||
sql::Column<uint64_t>("id"),
|
||||
sql::Column<ChannelId>("channelId"),
|
||||
sql::Column<std::string>("permId"),
|
||||
|
||||
sql::Column<permission::PermissionValue>("value"),
|
||||
sql::Column<permission::PermissionValue>("grant"),
|
||||
sql::Column<bool>("flag_skip"),
|
||||
sql::Column<bool>("flag_negate"),
|
||||
};
|
||||
|
||||
for(auto& permission : client_permissions) {
|
||||
|
||||
}
|
||||
|
||||
auto result = insert_query.execute(this->handle->getSql(), true);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
*/
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
}
|
||||
@@ -473,8 +473,10 @@ namespace terminal {
|
||||
if(cmd.arguments.size() < 1) {
|
||||
value = 1024;
|
||||
|
||||
rlimit limit{1024, 10000};
|
||||
setrlimit(7, &limit);
|
||||
rlimit rlimit{0, 0};
|
||||
getrlimit(RLIMIT_NOFILE, &rlimit);
|
||||
logMessage("RLimit: {}/{}", rlimit.rlim_cur, rlimit.rlim_max);
|
||||
//setrlimit(7, &limit);
|
||||
} else if(cmd.larguments[0] == "clear") {
|
||||
logMessage("Clearup leaks");
|
||||
for(auto& fd : fd_leaks)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by WolverinDEV on 11/04/2020.
|
||||
//
|
||||
|
||||
#include <snapshots/permission.h>
|
||||
|
||||
using namespace ts::server::snapshots;
|
||||
|
||||
void test_write() {
|
||||
std::string error{};
|
||||
size_t offset{0};
|
||||
ts::command_builder result{""};
|
||||
|
||||
permission_writer writer{type::TEAMSPEAK, 0, result, ts::permission::teamspeak::GroupType::GENERAL};
|
||||
std::deque<permission_entry> entries{};
|
||||
{
|
||||
{
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.type = ts::permission::resolvePermissionData("b_virtualserver_modify_host");
|
||||
entry.granted = {2, true};
|
||||
entry.value = {4, true};
|
||||
}
|
||||
{
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.type = ts::permission::resolvePermissionData("i_icon_id");
|
||||
entry.granted = {0, false};
|
||||
entry.value = {4, true};
|
||||
entry.flag_skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!writer.write(error, offset, entries)) {
|
||||
std::cerr << error << "\n";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Offset: " << offset << ". Command: " << result.build() << "\n";
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_write();
|
||||
}
|
||||
+1
-1
Submodule shared updated: ece70e4df4...707736d896
Reference in New Issue
Block a user