Compare commits

...

10 Commits

Author SHA1 Message Date
root d2ba1b4eee Totally fucked up... 2020-03-17 12:08:33 +01:00
WolverinDEV d6f483a019 totally fucked up 2020-03-17 12:08:07 +01:00
WolverinDEV 58666b8906 Some changes 2020-03-11 10:19:08 +01:00
WolverinDEV c002be7307 Merge branch '1.4.10-openssl' into 1.5.0 2020-03-10 17:04:45 +01:00
WolverinDEV 60a1c34dc9 Some more updates 2020-03-10 17:03:38 +01:00
WolverinDEV 799df15ace Some updates 2020-03-09 18:29:28 +01:00
WolverinDEV 7926a26091 Changed some stuff 2020-03-09 18:28:49 +01:00
WolverinDEV 6172628247 Some more changes 2020-03-05 14:10:22 +01:00
WolverinDEV 3a5c152b3c Added new files 2020-03-04 16:26:43 +01:00
WolverinDEV be4a99dcd7 Begining with the new VS structure 2020-03-04 16:24:36 +01:00
108 changed files with 5772 additions and 2044 deletions

@ -1 +1 @@
Subproject commit 9a26231c1f6c44f799419f87a3cac37a55425bdb
Subproject commit cba03a6316db76fdf056a960ee509ad20542ea44

View File

@ -4,6 +4,7 @@ project(TeaSpeak-Server)
set(CMAKE_VERBOSE_MAKEFILE ON)
#--allow-multiple-definition
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--unresolved-symbols=ignore-all")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
@ -32,8 +33,8 @@ add_definitions(-DUSE_BORINGSSL)
#3 = PRIVATE
option(BUILD_TYPE "Sets the build type" OFF)
option(BUILD_TYPE_NAME "Sets the build type name" OFF)
option(COMPILE_WEB_CLIENT "Enable/Disable the web cleint future" OFF)
#set(COMPILE_WEB_CLIENT "ON")
option(COMPILE_WEB_CLIENT "Enable/Disable the web client future" OFF)
set(COMPILE_WEB_CLIENT "OFF") #FIXME!
set(CMAKE_VERBOSE_MAKEFILE ON)
set(SERVER_SOURCE_FILES
@ -43,12 +44,11 @@ set(SERVER_SOURCE_FILES
# MySQLLibSSLFix.c
src/client/ConnectedClient.cpp
src/client/voice/PrecomputedPuzzles.cpp
src/server/udp-server/PrecomputedPuzzles.cpp
src/client/voice/VoiceClient.cpp
src/client/voice/VoiceClientHandschake.cpp
src/client/voice/VoiceClientCommandHandler.cpp
src/client/voice/VoiceClientPacketHandler.cpp
src/client/voice/VoiceClientView.cpp
src/TS3ServerClientManager.cpp
src/VirtualServer.cpp
src/TS3ServerHeartbeat.cpp
@ -138,7 +138,30 @@ set(SERVER_SOURCE_FILES
src/manager/ConversationManager.cpp
src/client/SpeakingClientHandshake.cpp
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
src/client/command_handler/music.cpp
src/client/command_handler/file.cpp
src/vserver/VirtualServerBase.cpp
src/vserver/VirtualServer.cpp
src/vserver/DefaultServer.cpp
src/services/PermissionsService.cpp
src/services/ClientChannelService.cpp
src/music/PlaylistPermissions.cpp
src/lincense/LicenseService.cpp
src/groups/GroupManager.cpp
src/groups/GroupAssignmentManager.cpp
src/groups/Group.cpp
src/services/VirtualServerInformation.cpp
src/vserver/VirtualServerManager.cpp
src/services/VirtualServerBroadcastService.cpp
src/server/udp-server/UDPServer.cpp
src/client/voice/PacketEncoder.cpp
src/client/voice/PacketDecoder.cpp
src/client/voice/PingHandler.cpp
src/client/query/QueryClientConnection.cpp
)
if (COMPILE_WEB_CLIENT)
add_definitions(-DCOMPILE_WEB_CLIENT)
@ -151,7 +174,7 @@ if (COMPILE_WEB_CLIENT)
src/client/web/WSWebClient.cpp
src/client/web/SampleHandler.cpp
src/client/web/VoiceBridge.cpp
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h)
)
endif ()
add_executable(PermHelper helpers/permgen.cpp)
@ -267,7 +290,6 @@ target_link_libraries(TeaSpeakServer
#Require a so
sqlite3
DataPipes::rtc::shared
breakpad::static
protobuf::libprotobuf
@ -281,8 +303,10 @@ target_link_libraries(TeaSpeakServer
)
if (COMPILE_WEB_CLIENT)
target_link_libraries(TeaSpeakServer ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
endif ()
target_link_libraries(TeaSpeakServer DataPipes::rtc::shared ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
else()
target_link_libraries(TeaSpeakServer DataPipes::core::shared)
endif()
# include_directories(${LIBRARY_PATH}/boringssl/include/)
target_link_libraries(TeaSpeakServer

View File

@ -6,7 +6,7 @@
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
using namespace std;
using namespace ts;

View File

@ -6,7 +6,7 @@
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
using namespace std;
using namespace ts;

View File

@ -1,58 +1,86 @@
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
CommandResult handleCommandClientMove(Command&);
CommandResult handleCommandClientGetVariables(Command&);
CommandResult handleCommandClientKick(Command&);
CommandResult handleCommandClientPoke(Command&);
General lock order:
- Client execute lock
- Server state lock
- Server channel tree
- Client channel tree
CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelEdit(Command&); write lock server channel tree
CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree
CommandResult handleCommandChannelMove(Command&); write lock server channel tree
When executing a command:
Lock order:
- Client execute lock
- Client state lock
- Server state lock (Server should not try to change state while a client is executing something)
Notes:
The server might be null or the default server.
This must be checked via the given macro!
CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock
Disconnect/unregister a client:
- Lock client execute lock
- Lock server state
- Unregister client from server and set server variable to nullptr
- Underlying server (UDP, Web, etc. disconnect)
CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel
CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel
Client channel tree connect/mode/disconnect:
- Lock client execute lock
- Lock server channel tree in write mode
- Lock client channel tree in write mode
- Move client & notify (notify required all clients to have their channel tree locked in shared mode)
CommandResult handleCommandPluginCmd(Command&);
Command lock overview:
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
CommandResult handleCommandClientMove(Command&);
CommandResult handleCommandClientGetVariables(Command&);
CommandResult handleCommandClientKick(Command&);
CommandResult handleCommandClientPoke(Command&);
CommandResult handleCommandClientMute(Command&);
CommandResult handleCommandClientUnmute(Command&);
CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelEdit(Command&); write lock server channel tree
CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree
CommandResult handleCommandChannelMove(Command&); write lock server channel tree
//Original from query but still reachable for all
CommandResult handleCommandClientList(Command&);
CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandClientFind(Command&);
CommandResult handleCommandClientInfo(Command&);
CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel
CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel
CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree
CommandResult handleCommandPluginCmd(Command&);
handleCommandChannelFind read lock: server channel tree
handleCommandChannelInfo read lock: server channel tree
CommandResult handleCommandClientMute(Command&);
CommandResult handleCommandClientUnmute(Command&);
General command handling: client_command_lock
Ensure that only one command at time will be handeled
//Original from query but still reachable for all
CommandResult handleCommandClientList(Command&);
CommandResult handleCommandClientFind(Command&);
CommandResult handleCommandClientInfo(Command&);
CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree
handleCommandChannelFind read lock: server channel tree
handleCommandChannelInfo read lock: server channel tree
General command handling: client_command_lock
Ensure that only one command at time will be handeled
Read access server channel tree: read lock channel_tree_lock
Write access server channel tree: lock channel_tree_lock
Write access client channel tree: read lock channel_tree_lock => lock client channel tree
if we write to the server channel tree no client should have their channel tree updated
Read access client channel tree: no lock required
Note: the server channel tree should not be accessed!
Read access server channel tree: read lock channel_tree_lock
Write access server channel tree: lock channel_tree_lock
Write access client channel tree: read lock channel_tree_lock => lock client channel tree
if we write to the server channel tree no client should have their channel tree updated
Read access client channel tree: no lock required
Note: the server channel tree should not be accessed!
Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree
Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree
TODO: Some kind of perm channel lock
TODO: Fix handleCommandChannelEdit
Test: Channel hide & show with clients! Multible clients as well!
Test: Channel hide & show with clients! Multiple clients as well!

9
server/namespaces Normal file
View File

@ -0,0 +1,9 @@
The general namespace prefix is ts::
TeaSpeak - Server: ts::server
Basic: ts::server
Sub-Server:
Query: ts::server::server::query
Voice: ts::server::server::udp
File: ts::server::server::file
Web: ts::server::server::web

View File

@ -159,10 +159,9 @@ 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(VirtualServerId server_id, v2::PermissionRegister* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
return command.query([&](int length, char** values, char** names){
permission::PermissionType key = permission::PermissionType::undefined;
permission::PermissionValue value = permNotGranted, granted = permNotGranted;
@ -198,6 +197,8 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
return 0;
}
if(channel_id > 0 && test_channel) {
//TODO: Test for invalid channels
/*
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
@ -205,6 +206,7 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
*/
}
@ -229,13 +231,12 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
std::shared_ptr<v2::PermissionRegister> DatabaseHelper::loadClientPermissionManager(VirtualServerId server_id, ClientDbId cldbid) {
#ifndef DISABLE_CACHING
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
if(permMgr->cldbid == cldbid && permMgr->sid == server_id) {
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
@ -249,14 +250,14 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
#endif
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
auto permission_manager = std::make_shared<v2::PermissionManager>();
auto permission_manager = std::make_shared<v2::PermissionRegister>();
bool loaded = false;
if(this->use_startup_cache && server) {
if(this->use_startup_cache) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server_id) {
entry = entries;
break;
}
@ -265,10 +266,8 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) {
auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr;
if(channel)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
if(perm->channelId > 0)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
else
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
}
@ -279,10 +278,10 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
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_id, permission_manager.get(), command, true));
}
@ -301,12 +300,11 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
}
void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ClientDbId client_dbid, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveClientPermissions(VirtualServerId server_id, ts::ClientDbId client_dbid, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
@ -321,7 +319,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":id", client_dbid},
variable{":chId", update.channel_id},
variable{":type", permission::SQL_PERM_USER},
@ -336,14 +334,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const std::shared_ptr<VirtualServer>& server, ts::GroupId group_id) {
auto result = std::make_shared<v2::PermissionManager>();
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadGroupPermissions(VirtualServerId server, ts::GroupId group_id, uint8_t type) {
auto result = std::make_shared<v2::PermissionRegister>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server) {
entry = entries;
break;
}
@ -361,19 +359,18 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
//7931
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveGroupPermissions(VirtualServerId server_id, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
@ -388,7 +385,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":id", group_id},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_GROUP},
@ -402,8 +399,8 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
}
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<permission::v2::PermissionManager> result;
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<permission::v2::PermissionRegister> result;
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
@ -416,7 +413,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
}
}
if(entry) {
result = std::make_shared<permission::v2::PermissionManager>();
result = std::make_shared<permission::v2::PermissionRegister>();
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) {
@ -431,16 +428,16 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
return result;
}
result = std::make_shared<permission::v2::PermissionManager>();
result = std::make_shared<permission::v2::PermissionRegister>();
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
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 ? server->getServerId() : 0, result.get(), command, false));
return result;
}
void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer> &server, PlaylistId pid, const std::shared_ptr<permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer> &server, PlaylistId pid, const std::shared_ptr<permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
@ -474,8 +471,8 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
}
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPermissions(const std::shared_ptr<VirtualServer>& server, ts::ChannelId channel) {
auto result = std::make_shared<v2::PermissionManager>();
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadChannelPermissions(const std::shared_ptr<VirtualServer>& server, ts::ChannelId channel) {
auto result = std::make_shared<v2::PermissionRegister>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
@ -502,11 +499,11 @@ 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 ? server->getServerId() : 0, result.get(), command, false));
return result;
}
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;

View File

@ -4,7 +4,7 @@
#include <map>
#include <vector>
#include <chrono>
#include <PermissionManager.h>
#include <PermissionRegister.h>
#include <Properties.h>
#include <cstdint>
@ -26,8 +26,8 @@ namespace ts {
struct CachedPermissionManager {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<permission::v2::PermissionManager> manager;
std::shared_ptr<permission::v2::PermissionManager> ownLock;
std::weak_ptr<permission::v2::PermissionRegister> manager;
std::shared_ptr<permission::v2::PermissionRegister> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
@ -91,17 +91,17 @@ namespace ts {
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadClientPermissionManager(VirtualServerId, ClientDbId);
void saveClientPermissions(VirtualServerId, ClientDbId , const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadGroupPermissions(VirtualServerId, GroupId, uint8_t /* group type */);
void saveGroupPermissions(VirtualServerId, GroupId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write

View File

@ -7,6 +7,7 @@
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/FileServer.h"
#include "src/groups/Group.h"
using namespace std;
using namespace std::chrono;
@ -21,7 +22,7 @@ Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId g
this->_properties = new Properties();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
this->setPermissionManager(make_shared<permission::v2::PermissionRegister>());
this->properties()[property::GROUP_ID] = groupId;
this->properties()[property::GROUP_TYPE] = type;
@ -59,7 +60,7 @@ Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId g
});
}
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager> &manager) {
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionRegister> &manager) {
this->_permissions = manager;
this->apply_properties_from_permissions();
}
@ -212,7 +213,7 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
group->properties()[property::GROUP_NAME] = targetName;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
@ -304,7 +305,7 @@ std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType t
std::shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
group->properties()[property::GROUP_NAME] = name;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
this->groups.push_back(group);
return group;
}
@ -344,7 +345,7 @@ bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const s
res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute();
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock(), target->groupId()));
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock() ? target->handle->server.lock()->getServerId() : 0, target->groupId(), (uint8_t) -1));
LOG_SQL_CMD(res);
return true;
}
@ -355,7 +356,7 @@ bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
return false;
}
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
return true;
}
@ -457,11 +458,6 @@ std::deque<property::ClientProperties> GroupManager::update_server_group_propert
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_server_groups.clear();
client->cached_server_groups.reserve(groups.size());
for(const auto& group : groups)
client->cached_server_groups.push_back(group->group->groupId());
}
}
@ -480,8 +476,6 @@ std::deque<property::ClientProperties> GroupManager::update_server_group_propert
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_channel_group = group->group->groupId();
}
}

View File

@ -4,7 +4,7 @@
#include <chrono>
#include <BasicChannel.h>
#include <ThreadPool/Mutex.h>
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "Properties.h"
#include "channel/ServerChannel.h"
#include "Definitions.h"
@ -29,7 +29,9 @@ namespace ts {
enum GroupTarget {
GROUPTARGET_SERVER,
GROUPTARGET_CHANNEL
GROUPTARGET_CHANNEL,
GROUPTARGET_UNKNOWN
};
enum GroupNameMode {
@ -82,7 +84,7 @@ namespace ts {
Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId);
~Group();
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
std::shared_ptr<permission::v2::PermissionRegister> permissions(){ return this->_permissions; }
Properties& properties(){ return *this->_properties; }
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
@ -133,10 +135,10 @@ namespace ts {
return data.has_value ? data.value : 0;
}
private:
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager);
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionRegister>& manager);
GroupManager* handle;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
std::shared_ptr<permission::v2::PermissionRegister> _permissions;
Properties* _properties;
GroupTarget _target;
GroupType _type;

View File

@ -23,6 +23,7 @@
#include <misc/strobf.h>
#include <jemalloc/jemalloc.h>
#include <protocol/buffers.h>
#include "src/server/udp-server/UDPServer.h"
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
@ -188,7 +189,7 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
this->banMgr = new BanManager(this->getSql());
this->banMgr = new bans::BanManager(this->getSql());
this->banMgr->loadBans();
this->web_list = make_shared<weblist::WebListManager>();
@ -389,6 +390,12 @@ FwIDAQAB
}
}
this->udpServer = new server::udp::Server{};
if(std::string error{}; !this->udpServer->initialize(error)) {
logCritical(LOG_INSTANCE, "Failed to allocate UDP server.");
return false;
}
this->voiceServerManager = new VirtualServerManager(this);
if (!this->voiceServerManager->initialize(true)) {
logCritical(LOG_INSTANCE, "Could not load servers!");
@ -433,22 +440,22 @@ void InstanceHandler::stopInstance() {
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
if (this->voiceServerManager)
this->voiceServerManager->shutdownAll(ts::config::messages::applicationStopped);
delete this->voiceServerManager;
this->voiceServerManager = nullptr;
delete std::exchange(this->voiceServerManager, nullptr);
debugMessage(LOG_INSTANCE, "All virtual server stopped");
debugMessage(LOG_QUERY, "Stopping query server");
if (this->queryServer) this->queryServer->stop();
delete this->queryServer;
this->queryServer = nullptr;
delete std::exchange(this->queryServer, nullptr);
debugMessage(LOG_QUERY, "Query server stopped");
debugMessage(LOG_FT, "Stopping file server");
if (this->fileServer) this->fileServer->stop();
delete this->fileServer;
this->fileServer = nullptr;
delete std::exchange(this->fileServer, nullptr);
debugMessage(LOG_FT, "File server stopped");
if(this->udpServer) this->udpServer->finalize();
delete std::exchange(this->udpServer, nullptr);
this->save_channel_permissions();
this->save_group_permissions();
@ -569,7 +576,7 @@ void InstanceHandler::save_group_permissions() {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, group->groupId(), permissions);
serverInstance->databaseHelper()->saveGroupPermissions(0, group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(0, "Saved instance group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}

View File

@ -23,6 +23,10 @@ namespace ts {
class LicenseService;
}
namespace server::udp {
class Server;
}
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@ -45,10 +49,12 @@ namespace ts {
FileServer* getFileServer(){ return fileServer; }
QueryServer* getQueryServer(){ return queryServer; }
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
BanManager* banManager(){ return this->banMgr; }
bans::BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
[[nodiscard]] inline auto udp_server() { return this->udpServer; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
void executeTick(VirtualServer*);
@ -112,9 +118,11 @@ namespace ts {
FileServer* fileServer = nullptr;
QueryServer* queryServer = nullptr;
server::udp::Server* udpServer{nullptr};
VirtualServerManager* voiceServerManager = nullptr;
DatabaseHelper* dbHelper = nullptr;
BanManager* banMgr = nullptr;
bans::BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
ts::Properties* _properties = nullptr;

View File

@ -707,7 +707,7 @@ struct PermissionCommandTuple {
ChannelId channel;
};
inline bool writePermissions(const shared_ptr<permission::v2::PermissionManager>& manager, Command& cmd, int& index, int version, permission::teamspeak::GroupType type, std::string& error) {
inline bool writePermissions(const shared_ptr<permission::v2::PermissionRegister>& manager, Command& cmd, int& index, int version, permission::teamspeak::GroupType type, std::string& error) {
for(const auto& permission_container : manager->permissions()) {
auto permission = get<1>(permission_container);
SnapshotPermissionEntry{

View File

@ -42,8 +42,8 @@ void ts::server::shutdownInstance(const std::string& message) {
mainThreadActive = false;
}
std::shared_ptr<server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<server::ShutdownData> server::scheduledShutdown() { return currentShutdown; }
std::shared_ptr<ts::server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<ts::server::ShutdownData> ts::server::scheduledShutdown() { return currentShutdown; }
inline void broadcastMessage(const std::string& message) {
if(!serverInstance || !serverInstance->getVoiceServerManager())
@ -57,8 +57,8 @@ inline void broadcastMessage(const std::string& message) {
}
void executeScheduledShutdown(const std::shared_ptr<ShutdownData>& data);
bool server::scheduleShutdown(const std::chrono::system_clock::time_point& time, const std::string& reason) {
server::cancelShutdown(false); //Cancel old shutdown
bool ts::server::scheduleShutdown(const std::chrono::system_clock::time_point& time, const std::string& reason) {
ts::server::cancelShutdown(false); //Cancel old shutdown
auto data = std::make_shared<ShutdownData>();
data->active = true;
@ -73,13 +73,13 @@ bool server::scheduleShutdown(const std::chrono::system_clock::time_point& time,
return true;
}
void server::cancelShutdown(bool notify) {
void ts::server::cancelShutdown(bool notify) {
if(!currentShutdown) return;
if(notify && !config::messages::shutdown::canceled.empty()) {
broadcastMessage(config::messages::shutdown::canceled);
}
auto current = server::scheduledShutdown();
auto current = ts::server::scheduledShutdown();
current->active = false;
current->shutdownNotify.notify_all();
if(!threads::save_join(current->shutdown_thread)) {

View File

@ -88,7 +88,7 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
sassert(cl->state == ConnectionState::DISCONNECTED);
sassert(cl->state == ClientState::DISCONNECTED);
}
auto client_id = cl->getClientId();
@ -122,13 +122,13 @@ bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
}
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->serverId, cl->getClientDatabaseId(), cl->clientPermissions);
cl->setClientId(0);
return true;
}
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::CONNECTED;
client->state = ClientState::CONNECTED;
{
lock_guard lock(this->clients.lock);
if(client->getClientId() > 0) {
@ -152,7 +152,7 @@ void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> clie
}
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::DISCONNECTED;
client->state = ClientState::DISCONNECTED;
{
auto client_id = client->getClientId();
@ -259,14 +259,16 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
ChannelId old_channel_id{0};
if(target->currentChannel) {
old_channel_id = target->currentChannel->channelId();
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
client->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
@ -275,7 +277,7 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
target->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
target->currentChannel = nullptr;
}
@ -294,14 +296,16 @@ void VirtualServer::notify_client_kick(
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
ChannelId old_channel_id{0};
if(target->currentChannel) {
old_channel_id = target->currentChannel->channelId();
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
client->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
@ -310,7 +314,7 @@ void VirtualServer::notify_client_kick(
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
target->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
target->currentChannel = nullptr;
}
}

View File

@ -152,7 +152,7 @@ void VirtualServer::executeServerTick() {
if(cl->clientPermissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->serverId, cl->getClientDatabaseId(), cl->clientPermissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast<milliseconds>(end - begin).count());
}
@ -250,7 +250,7 @@ void VirtualServer::executeServerTick() {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(this->ref(), group->groupId(), permissions);
serverInstance->databaseHelper()->saveGroupPermissions(this->serverId, group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}

View File

@ -24,6 +24,7 @@
#include "Configuration.h"
#include "VirtualServer.h"
#include "src/manager/ConversationManager.h"
#include "src/server/udp-server/UDPServer.h"
#include <misc/sassert.h>
using namespace std;
@ -154,7 +155,7 @@ bool VirtualServer::initialize(bool test_properties) {
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>())
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this);
this->tokenManager = new ts::server::tokens::TokenManager(this);
this->tokenManager->loadTokens();
this->complains = new ComplainManager(this);
@ -313,64 +314,65 @@ bool VirtualServer::start(std::string& error) {
}
}
auto host = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
{
auto host = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
if(host.empty()){
error = "invalid host (\"" + host + "\")";
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
}
deque<shared_ptr<VoiceServerBinding>> bindings;
for(const auto& address : split_hosts(host, ',')) {
auto entry = make_shared<VoiceServerBinding>();
if(net::is_ipv4(address)) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else if(net::is_ipv6(address)) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else {
logError(this->serverId, "Failed to determinate address type for \"{}\"", address);
continue;
if(host.empty()){
error = "invalid host (\"" + host + "\")";
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
}
bindings.push_back(entry);
}
if(bindings.empty()) {
error = "failed to resole any host!";
this->stop("failed to start", false);
return false;
}
//Setup voice server
udpVoiceServer = make_shared<VoiceServer>(self.lock());
if(!udpVoiceServer->start(bindings, error)) {
error = "could not start voice server. Message: " + error;
this->stop("failed to start", false);
return false;
deque<shared_ptr<VoiceServerBinding>> bindings;
for(const auto& address : split_hosts(host, ',')) {
auto entry = make_shared<VoiceServerBinding>();
if(net::is_ipv4(address)) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else if(net::is_ipv6(address)) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else {
logError(this->serverId, "Failed to determinate address type for \"{}\"", address);
continue;
}
bindings.push_back(entry);
}
if(bindings.empty()) {
error = "failed to resole any host!";
this->stop("failed to start", false);
return false;
}
auto result = serverInstance->udp_server()->register_virtual_server(this);
if(result != server::udp::ServerRegisterResult::SUCCESS) {
error = "failed to start udp voice server (" + std::to_string((int) result) + ")";
this->stop("failed to start", false);
return false;
}
}
if(ts::config::web::activated && serverInstance->sslManager()->web_ssl_options()) {
@ -501,9 +503,7 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
this->musicManager->disconnectBots();
serverInstance->cancelExecute(this);
if(this->udpVoiceServer) this->udpVoiceServer->stop();
this->udpVoiceServer = nullptr;
serverInstance->udp_server()->unregister_virtual_server(this);
#ifdef COMPILE_WEB_CLIENT
if(this->webControlServer) this->webControlServer->stop();
@ -637,30 +637,26 @@ std::shared_ptr<ConnectedClient> VirtualServer::findClient(std::string name, boo
}
bool VirtualServer::forEachClient(std::function<void(std::shared_ptr<ConnectedClient>)> function) {
for(const auto& elm : this->getClients()) {
shared_lock close_lock(elm->finalDisconnectLock, try_to_lock_t{});
if(close_lock.owns_lock()) //If not locked than client is on the way to disconnect
if(elm->state == ConnectionState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL) {
function(elm);
}
}
for(const auto& elm : this->getClients())
if(elm->state == ClientState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL)
function(elm);
return true;
}
std::vector<std::shared_ptr<ConnectedClient>> VirtualServer::getClients() {
vector<shared_ptr<ConnectedClient>> clients;
vector<shared_ptr<ConnectedClient>> result{};
{
lock_guard lock(this->clients.lock);
clients.reserve(this->clients.count);
result.reserve(this->clients.count);
for(auto& client : this->clients.clients) {
if(!client) continue;
clients.push_back(client);
result.push_back(client);
}
}
return clients;
return result;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannel(std::shared_ptr<BasicChannel> channel) {
@ -677,7 +673,7 @@ deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannel(std::share
for(const auto& weak_client : weak_clients) {
auto client = weak_client.lock();
if(!client) continue;
if(client->connectionState() != ConnectionState::CONNECTED) continue;
if(client->connectionState() != ClientState::CONNECTED) continue;
if(client->getChannel() != channel) continue; /* to be sure */
result.push_back(move(client));
@ -792,7 +788,7 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
}
if(!cache->client_permissions) {
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(self.lock(), client_dbid);
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->serverId, client_dbid);
}
bool have_skip_permission = false;
@ -1108,7 +1104,7 @@ bool VirtualServer::resetPermissions(std::string& token) {
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId();
auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
auto created = this->tokenManager->createToken(ts::server::tokens::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
if(!created) {
logCritical(this->serverId, "Failed to generate default serveradmin token!");
} else {
@ -1189,7 +1185,7 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
for(const auto& client : this->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
if(client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();

View File

@ -106,7 +106,7 @@ namespace ts {
bool global_skip = false;
bool global_skip_set = false;
std::shared_ptr<permission::v2::PermissionManager> client_permissions;
std::shared_ptr<permission::v2::PermissionRegister> client_permissions;
std::vector<std::shared_ptr<GroupAssignment>> assignment_server_groups;
bool assignment_server_groups_set = false;
@ -204,7 +204,6 @@ namespace ts {
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return serverStatistics; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return this->udpVoiceServer; }
WebControlServer* getWebServer(){ return this->webControlServer; }
/* calculate permissions for an client in this server */
@ -288,9 +287,8 @@ namespace ts {
std::chrono::system_clock::time_point lastTick;
void executeServerTick();
std::shared_ptr<VoiceServer> udpVoiceServer = nullptr;
WebControlServer* webControlServer = nullptr;
token::TokenManager* tokenManager = nullptr;
ts::server::tokens::TokenManager* tokenManager = nullptr;
ComplainManager* complains = nullptr;
letter::LetterManager* letters = nullptr;
std::shared_ptr<music::MusicBotManager> musicManager;

View File

@ -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 server::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,7 @@ 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);
this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize);
logMessage(LOG_INSTANCE, "Puzzles generated! Time required: " + to_string(duration_cast<milliseconds>(system_clock::now() - start).count()) + "ms");
size_t serverCount = 0;
@ -444,6 +444,6 @@ void VirtualServerManager::tickHandshakeClients() {
for(const auto& server : this->serverInstances()) {
auto vserver = server->getVoiceServer();
if(vserver)
vserver->tickHandshakingClients();
vserver->tickClients();
}
}

View File

@ -2,7 +2,7 @@
#include <deque>
#include <EventLoop.h>
#include "client/voice/PrecomputedPuzzles.h"
#include "src/server/udp-server/PrecomputedPuzzles.h"
#include "server/VoiceIOManager.h"
#include "VirtualServer.h"
@ -57,7 +57,7 @@ namespace ts {
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 &);
protocol::PuzzleManager* rsaPuzzles() { return this->puzzles; }
server::udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
@ -80,7 +80,7 @@ namespace ts {
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
protocol::PuzzleManager* puzzles = nullptr;
server::udp::PuzzleManager* puzzles = nullptr;
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;

View File

@ -1,11 +1,11 @@
#pragma once
#include <lock/rw_mutex.h>
#include <stdint.h>
#include <cstdlib>
#include "Properties.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "BasicChannel.h"
#include "../Group.h"
#include <memory>
#include <sql/SqlQuery.h>
@ -45,7 +45,7 @@ namespace ts {
void deleteSemiPermanentChannels();
std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
[[nodiscard]] std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
protected:
virtual ChannelId generateChannelId() override;

View File

@ -21,7 +21,7 @@ using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
extern ts::server::InstanceHandler* serverInstance;
@ -451,11 +451,10 @@ bool ConnectedClient::notifyClientLeftView(
return true;
}
bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree,
const std::shared_ptr<BasicChannel> &target_channel) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client->currentChannel || &*client == this);
@ -479,7 +478,7 @@ bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<Connected
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["cfid"] = channel_from;
cmd["ctid"] = target_channel ? target_channel->channelId() : 0;
cmd["reasonid"] = (uint8_t) (target_channel ? ViewReasonId::VREASON_CHANNEL_KICK : ViewReasonId::VREASON_SERVER_KICK);
cmd["reasonmsg"] = message;
@ -494,12 +493,10 @@ bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<Connected
return true;
}
bool ConnectedClient::notifyClientLeftViewBanned(
const shared_ptr<ConnectedClient> &client,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
size_t length,
bool lock_channel_tree) {
bool ConnectedClient::notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
@ -508,7 +505,7 @@ bool ConnectedClient::notifyClientLeftViewBanned(
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["cfid"] = channel_from;
cmd["ctid"] = 0;
cmd["reasonid"] = ViewReasonId::VREASON_BAN;
cmd["reasonmsg"] = message;
@ -538,7 +535,7 @@ bool ConnectedClient::notifyClientLeftViewBanned(
}
bool ConnectedClient::sendNeededPermissions(bool enforce) {
if(!enforce && this->state != ConnectionState::CONNECTED) return false;
if(!enforce && this->state != ClientState::CONNECTED) return false;
if(!enforce && chrono::system_clock::now() - this->lastNeededNotify < chrono::seconds(5) && this->lastNeededPermissionNotifyChannel == this->currentChannel) { //Dont spam these (hang up ui)
this->requireNeededPermissionResend = true;
@ -563,6 +560,10 @@ bool ConnectedClient::notifyClientNeededPermissions() {
cmd[index]["permid"] = value.first;
cmd[index++]["permvalue"] = value.second.has_value ? value.second.value : 0;
}
if(index == 0) {
cmd[index]["permid"] = permission::i_client_talk_power;
cmd[index++]["permvalue"] = 0;
}
if(index == 0) {
cmd[index]["permid"] = permission::i_client_talk_power;
@ -719,7 +720,7 @@ void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>
void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
if(this->state == ConnectionState::CONNECTED) {
if(this->state == ClientState::CONNECTED) {
if(this->requireNeededPermissionResend)
this->sendNeededPermissions(false);
if(this->lastOnlineTimestamp.time_since_epoch().count() == 0) {
@ -832,7 +833,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
if(generateReturnStatus)
this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as<std::string>() : "");
if(result.error_code() != error::ok && this->state == ConnectionState::INIT_HIGH)
if(result.error_code() != error::ok && this->state == ClientState::INITIALIZING)
this->close_connection(system_clock::now()); //Disconnect now
for (const auto& handler : postCommandHandler)
@ -850,13 +851,13 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
return true;
}
std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
std::shared_ptr<bans::BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr;
//Check if manager banned
auto banManager = serverInstance->banManager();
shared_ptr<BanRecord> banEntry = nullptr;
deque<shared_ptr<BanRecord>> entries;
shared_ptr<bans::BanRecord> banEntry = nullptr;
deque<shared_ptr<bans::BanRecord>> entries;
if (!banEntry) {
banEntry = banManager->findBanByName(this->server->getServerId(), this->getDisplayName());

View File

@ -8,6 +8,7 @@
#include "../channel/ClientChannelView.h"
#include "DataClient.h"
#include "query/command3.h"
#include "src/manager/BanManager.h"
#define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]")
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
@ -67,14 +68,13 @@ namespace ts {
friend class QueryServer;
friend class DataClient;
friend class SpeakingClient;
friend class connection::VoiceClientConnection;
friend class ts::GroupManager;
friend class VirtualServerManager;
public:
explicit ConnectedClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>& server);
~ConnectedClient() override;
ConnectionState connectionState(){ return this->state; }
ClientState connectionState(){ return this->state; }
std::string getLoggingPeerIp() { return config::server::disable_ip_saving || (this->server && this->server->disable_ip_saving()) ? "X.X.X.X" : this->getPeerIp(); }
std::string getPeerIp(){ return this->isAddressV4() ? net::to_string(this->getAddressV4()->sin_addr) : this->isAddressV6() ? net::to_string(this->getAddressV6()->sin6_addr) : "localhost"; }
uint16_t getPeerPort(){ return ntohs(this->isAddressV4() ? this->getAddressV4()->sin_port : this->isAddressV6() ? this->getAddressV6()->sin6_port : (uint16_t) 0); }
@ -96,6 +96,8 @@ namespace ts {
virtual void setClientId(uint16_t clId) { properties()[property::CLIENT_ID] = clId; }
inline std::shared_ptr<BasicChannel> getChannel(){ return this->currentChannel; }
/* ATTENTION: Do this only with command_lock locked, and client_channel_tree lock lock in exclusive mode */
inline void setChannel(const std::shared_ptr<BasicChannel>& channel){ this->currentChannel = channel; }
inline ChannelId getChannelId(){ auto channel = this->currentChannel; return channel ? channel->channelId() : 0; }
inline std::shared_ptr<VirtualServer> getServer(){ return this->server; }
inline ServerId getServerId(){ return this->server ? this->server->getServerId() : (ServerId) 0; }
@ -122,7 +124,7 @@ namespace ts {
virtual bool notifyClientNeededPermissions();
virtual bool notifyServerGroupList();
virtual bool notifyGroupPermList(const std::shared_ptr<Group>&, bool);
virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr<permission::v2::PermissionManager>&, bool);
virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr<permission::v2::PermissionRegister>&, bool);
virtual bool notifyChannelGroupList();
virtual bool notifyConnectionInfo(const std::shared_ptr<ConnectedClient> &target, const std::shared_ptr<ConnectionInfoData> &info);
virtual bool notifyChannelSubscribed(const std::deque<std::shared_ptr<BasicChannel>> &);
@ -210,20 +212,13 @@ namespace ts {
const ViewReasonServerLeftT& /* mode */
);
virtual bool notifyClientLeftViewKicked(
const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree
);
virtual bool notifyClientLeftViewBanned(
const std::shared_ptr<ConnectedClient> &client,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
size_t length,
bool lock_channel_tree
);
virtual bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree, const std::shared_ptr<BasicChannel> &target_channel);
virtual bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message);
virtual bool notifyMusicPlayerSongChange(const std::shared_ptr<MusicClient>& bot, const std::shared_ptr<music::SongInfo>& newEntry);
virtual bool notifyMusicQueueAdd(const std::shared_ptr<MusicClient>& bot, const std::shared_ptr<ts::music::SongInfo>& entry, int index, const std::shared_ptr<ConnectedClient>& invoker);
@ -249,7 +244,7 @@ namespace ts {
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
void updateTalkRights(permission::PermissionValue talk_power);
virtual std::shared_ptr<BanRecord> resolveActiveBan(const std::string& ip_address);
virtual std::shared_ptr<bans::BanRecord> resolveActiveBan(const std::string& ip_address);
inline std::shared_ptr<stats::ConnectionStatistics> getConnectionStatistics() {
return this->connectionStatistics;
@ -277,43 +272,27 @@ namespace ts {
*/
bool update_cached_permissions();
std::shared_lock<std::shared_mutex> require_connected_state(bool blocking = false) {
//try_to_lock_t
std::shared_lock<std::shared_mutex> disconnect_lock{};
if(blocking) [[unlikely]]
disconnect_lock = std::shared_lock{this->finalDisconnectLock};
else
disconnect_lock = std::shared_lock{this->finalDisconnectLock, std::try_to_lock};
if(!disconnect_lock) [[unlikely]]
return disconnect_lock;
{
std::lock_guard state_lock{this->state_lock};
if(this->state != ConnectionState::CONNECTED)
return {};
}
return disconnect_lock;
}
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->_subscribed_playlist.lock() == playlist;
}
[[nodiscard]] auto require_connected_state() {
return ts::rwshared_lock{this->state_lock};
}
template <typename T = std::lock_guard<threads::Mutex>>
[[nodiscard]] inline auto lock_command_handling() { return T{this->command_lock}; }
void increase_join_state() { this->join_state_id++; }
protected:
std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address;
//General states
std::mutex state_lock;
ConnectionState state{ConnectionState::UNKNWON};
ts::rw_mutex state_lock{};
ClientState state{ClientState::UNKNWON};
bool allowedToTalk = false;
std::shared_mutex finalDisconnectLock; /* locked before state lock! */
std::vector<GroupId> cached_server_groups{}; /* variable locked with channel_lock */
GroupId cached_channel_group = 0; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> visibleClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> mutedClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> openChats{}; /* variable locked with channel_lock */
@ -650,7 +629,7 @@ namespace ts {
this->connection_lock = std::move(other.connection_lock);
}
inline bool valid() const { return !!this->client && !!this->connection_lock; }
inline bool valid() const { return !!this->client && this->connection_lock.shared_locked() && client->connectionState() == ClientState::CONNECTED; }
inline operator bool() const { return this->valid(); }
@ -664,7 +643,7 @@ namespace ts {
T &operator*() { return *this->client; }
std::shared_ptr<T> client;
std::shared_lock<std::shared_mutex> connection_lock{};
ts::rwshared_lock<ts::rw_mutex> connection_lock{};
};
}
}

View File

@ -12,7 +12,7 @@
#include "../server/file/FileServer.h"
#include "../server/VoiceServer.h"
#include "voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../InstanceHandler.h"
#include "../server/QueryServer.h"
#include "file/FileClient.h"

View File

@ -19,7 +19,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
extern ts::server::InstanceHandler* serverInstance;
@ -133,7 +133,7 @@ bool ConnectedClient::notifyGroupPermList(const std::shared_ptr<Group>& group, b
return true;
}
bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr<permission::v2::PermissionManager>& mgr, bool perm_sids) {
bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr<permission::v2::PermissionRegister>& mgr, bool perm_sids) {
Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientpermlist" : "");
auto permissions = mgr->permissions();

View File

@ -629,6 +629,7 @@ bool ConnectedClient::handle_text_command(
if(!vc) return false;
send_message(_this.lock(), "Packet generations:");
auto& id_generator = vc->getConnection()->packet_encoder().id_generator();
for(const auto& type : {
protocol::PacketTypeInfo::Command,
protocol::PacketTypeInfo::CommandLow,
@ -639,8 +640,8 @@ bool ConnectedClient::handle_text_command(
protocol::PacketTypeInfo::Ping,
protocol::PacketTypeInfo::Pong}) {
auto id = vc->getConnection()->getPacketIdManager().currentPacketId(type);
auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
auto id = id_generator.currentPacketId(type);
auto gen = id_generator.generationId(type);
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()));

View File

@ -112,7 +112,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
}
this->_properties->toggleSave(true);
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server, this->getClientDatabaseId());
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server ? ref_server->serverId : 0, this->getClientDatabaseId());
//Setup / fix stuff
if(!this->properties()[property::CLIENT_FLAG_AVATAR].as<string>().empty()){

View File

@ -98,14 +98,17 @@ namespace ts {
virtual bool loadDataForCurrentServer();
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionRegister> permissions() { return this->clientPermissions; }
protected:
sql::SqlManager* sql;
std::shared_ptr<VirtualServer> server;
std::shared_ptr<permission::v2::PermissionManager> clientPermissions = nullptr;
std::shared_ptr<permission::v2::PermissionRegister> clientPermissions = nullptr;
std::shared_ptr<Properties> _properties;
std::shared_ptr<BasicChannel> currentChannel = nullptr;
/* the current channel is save to access when the server channel tree has been locked (shared or exclusively) */
std::shared_ptr<BasicChannel> currentChannel{nullptr};
};
}
}

View File

@ -6,7 +6,7 @@ namespace ts {
namespace server {
class InternalClient : public ConnectedClient {
public:
InternalClient(sql::SqlManager*, const std::shared_ptr<server::VirtualServer>&, std::string, bool);
InternalClient(sql::SqlManager*, const std::shared_ptr<ts::server::VirtualServer>&, std::string, bool);
~InternalClient();
void setSharedLock(const std::shared_ptr<ConnectedClient>& _this){

View File

@ -174,6 +174,7 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
#endif
deque<shared_ptr<SpeakingClient>> available_clients;
auto type_id_string = std::to_string(type_id);
for(const auto& client : this->server->getClients()) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
if(!speakingClient || client == this) continue;
@ -185,16 +186,15 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
if(type_id == 0)
available_clients.push_back(speakingClient);
else {
shared_lock client_lock(this->channel_lock);
for(const auto& id : client->cached_server_groups) {
if(id == type_id) {
available_clients.push_back(speakingClient);
break;
}
}
auto client_groups = client->properties()[property::CLIENT_SERVERGROUPS].as<std::string>();
auto index = client_groups.find(client_groups);
if(index == std::string::npos) continue;
if(index != client_groups.length() && client_groups[index] != ',') continue;
available_clients.push_back(speakingClient);
}
} else if(type == WhisperType::CHANNEL_GROUP) {
if(client->cached_channel_group == type_id)
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as_save<GroupId>() == type_id)
available_clients.push_back(speakingClient);
} else if(type == WhisperType::CHANNEL_COMMANDER) {
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
@ -622,7 +622,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
{
for(const auto &cl : this->server->getClients())
if((cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_WEB || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_MUSIC))
if(cl->connectionState() <= ConnectionState::CONNECTED && cl->connectionState() >= ConnectionState::INIT_HIGH)
if(cl->connectionState() <= ClientState::CONNECTED && cl->connectionState() >= ClientState::CONNECTED) //TODO: Get "real" state and do not ignore just initializing clients!
count++;
}
@ -653,19 +653,8 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
}
}
this->postCommandHandler.emplace_back([&](){
auto self = dynamic_pointer_cast<SpeakingClient>(_this.lock());
std::thread([self](){
if(self->state != ConnectionState::INIT_HIGH) return;
try {
self->processJoin();
} catch (std::exception& ex) {
logError(self->getServerId(), "Failed to proceed client join for {}. Got exception with message {}", CLIENT_STR_LOG_PREFIX_(self), ex.what());
self->close_connection(chrono::system_clock::now() + chrono::seconds{5});
}
}).detach();
});
/* we're not doing this anymore from a different thread */
this->processJoin();
debugMessage(this->getServerId(), "{} Client init timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
return command_result{error::ok};
}
@ -679,7 +668,7 @@ void SpeakingClient::processJoin() {
this->resetIdleTime();
threads::MutexLock lock(this->command_lock); //Don't process any commands!
if(this->state != ConnectionState::INIT_HIGH) {
if(this->state != ClientState::INITIALIZING) {
logError(this->getServerId(), "{} Invalid processJoin() connection state!", CLIENT_STR_LOG_PREFIX);
return;
}
@ -729,7 +718,7 @@ void SpeakingClient::processJoin() {
TIMING_STEP(timings, "assign chan");
this->sendChannelList(true);
this->state = ConnectionState::CONNECTED;
this->state = ClientState::CONNECTED;
TIMING_STEP(timings, "send chan t");
/* trick the join method */
@ -767,7 +756,7 @@ void SpeakingClient::processJoin() {
void SpeakingClient::processLeave() {
auto ownLock = _this.lock();
auto server = this->getServer();
if(server){
if(server) {
logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()));
{
unique_lock server_channel_lock(this->server->channel_tree_lock);
@ -777,14 +766,6 @@ void SpeakingClient::processLeave() {
server->musicManager->cleanup_client_bots(this->getClientDatabaseId());
//ref_server = nullptr; Removed caused nullptr exceptions
}
{ //Delete own viewing clients
/*
* No need, are only weak references!
threads::MutexLock l(this->viewLock);
this->visibleClients.clear();
this->mutedClients.clear();
*/
}
}
void SpeakingClient::triggerVoiceEnd() {
@ -815,7 +796,7 @@ void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "SpeakingClient::tick", milliseconds(2));
this->updateSpeak(true, time);
if(this->state == ConnectionState::CONNECTED) {
if(this->state == ClientState::CONNECTED) {
if(this->max_idle_time.has_value) {
auto max_idle = this->max_idle_time.value;
if(max_idle > 0 && this->idleTimestamp.time_since_epoch().count() > 0 && duration_cast<seconds>(time - this->idleTimestamp).count() > max_idle) {
@ -833,7 +814,7 @@ void SpeakingClient::updateChannelClientProperties(bool channel_lock, bool notif
}
command_result SpeakingClient::handleCommand(Command &command) {
if(this->connectionState() == ConnectionState::INIT_HIGH) {
if(this->connectionState() == ClientState::INITIALIZING) {
if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) {
command_result result;
if(command.command() == "handshakebegin")

View File

@ -8,7 +8,7 @@
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -32,7 +32,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
//{findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as<string>() : cmd[index]["permsid"].as<string>())}; \
//TODO: Log missing permissions?
@ -1654,7 +1654,7 @@ command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd)
ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelclient_permission_list, 1, channel_id);
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"].as<ClientDbId>());
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cmd["cldbid"].as<ClientDbId>());
Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelclientpermlist" : "");
@ -1714,7 +1714,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if(!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cldbid);
{
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
@ -1740,7 +1740,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, cldbid, mgr);
if (!cll.empty()) {
for (const auto &elm : cll) {
if(elm->update_cached_permissions()) /* update cached calculated permissions */
@ -1784,7 +1784,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if(!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cldbid);
{
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
@ -1821,7 +1821,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, cldbid, mgr);
if (!onlineClientInstances.empty())
for (const auto &elm : onlineClientInstances) {
if (elm->update_cached_permissions()) /* update cached calculated permissions */

View File

@ -7,7 +7,7 @@
#include "../InternalClient.h"
#include "../../server/file/FileServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -32,7 +32,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12
@ -867,7 +867,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
auto cldbid = cmd["cldbid"].as<ClientDbId>();
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
return command_result{error::client_invalid_id};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->getServerId() : 0, cldbid);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true);
@ -897,7 +897,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
update_channels |= permission_is_client_property(permType);
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->getServerId() : 0, cldbid, mgr);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
if (!onlineClients.empty())
for (const auto &elm : onlineClients) {
@ -919,7 +919,7 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
auto cldbid = cmd["cldbid"].as<ClientDbId>();
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
return command_result{error::client_invalid_id};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cldbid);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
@ -943,7 +943,7 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, cldbid, mgr);
if (!onlineClients.empty())
for (const auto &elm : onlineClients) {
if(elm->update_cached_permissions()) /* update cached calculated permissions */
@ -962,7 +962,7 @@ command_result ConnectedClient::handleCommandClientPermList(Command &cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_permission_list, 1);
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->getServerId() : 0, cmd["cldbid"]);
if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return command_result{error::database_empty_result};
return command_result{error::ok};
}

View File

@ -16,7 +16,7 @@
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -48,7 +48,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12

View File

@ -16,7 +16,7 @@
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -48,7 +48,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12
@ -460,6 +460,7 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
}
}
//TODO: When using new VS model, check for a temporary assignment!
this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel);
for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) {
unique_lock client_channel_lock_w(targetClient->channel_lock);
@ -568,7 +569,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return command_result{error::ok};
for(const auto& client : this->server->getClients()) {
if (client->connectionState() != ConnectionState::CONNECTED)
if (client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();
@ -1560,7 +1561,7 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) {
auto server_groups = this->server->getGroupManager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK);
auto channel_group = this->server->getGroupManager()->getChannelGroup(client_dbid, channel, true);
auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServer(), client_dbid);
auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), client_dbid);
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : "");
size_t index = 0;
@ -2525,7 +2526,7 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
auto delete_count = current_conversation->delete_messages(timestamp_end, limit, timestamp_begin, bulk["cldbid"]);
if(delete_count > 0) {
for(const auto& client : ref_server->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
if(client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();

View File

@ -16,7 +16,7 @@
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -49,7 +49,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};

View File

@ -16,7 +16,7 @@
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@ -48,7 +48,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12

View File

@ -101,7 +101,7 @@ bool MusicClient::disconnect(const std::string &reason) {
return true;
}
bool server::MusicClient::notifyClientMoved(
bool MusicClient::notifyClientMoved(
const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
ViewReasonId reason,

View File

@ -8,6 +8,7 @@
#include "src/InstanceHandler.h"
#include <pipes/errors.h>
#include <misc/std_unique_ptr.h>
#include "./QueryClientConnection.h"
using namespace std;
using namespace std::chrono;
@ -20,41 +21,20 @@ using namespace ts::server;
//#define DEBUG_TRAFFIC
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle), clientFd(sockfd) {
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle) {
memtrack::allocated<QueryClient>(this);
int enabled = 1;
int disabled = 0;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) {
logError(this->getServerId(), "[Query] Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
}
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0) {
logError(this->getServerId(), "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
}
this->readEvent = event_new(this->handle->eventLoop, this->clientFd, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageRead(a, b, c); }, this);
this->writeEvent = event_new(this->handle->eventLoop, this->clientFd, EV_WRITE, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageWrite(a, b, c); }, this);
this->connection = new server::query::QueryClientConnection{this, sockfd};
this->state = ConnectionState::CONNECTED;
this->state = ClientState::INITIALIZING;
connectedTimestamp = system_clock::now();
this->resetEventMask();
}
void QueryClient::applySelfLock(const std::shared_ptr<ts::server::QueryClient> &cl) {
bool QueryClient::initialize(std::string& error, const std::shared_ptr<ts::server::QueryClient> &cl) {
this->_this = cl;
}
QueryClient::~QueryClient() {
memtrack::freed<QueryClient>(this);
// if(this->closeLock.tryLock() != 0)
// logCritical("Query manager deleted, but is still in usage! (closeLock)");
// if(this->bufferLock.tryLock() != 0)
// logCritical("Query manager deleted, but is still in usage! (bufferLock)");
this->ssl_handler.finalize();
}
void QueryClient::preInitialize() {
this->properties()[property::CLIENT_TYPE] = ClientType::CLIENT_QUERY;
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_QUERY;
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = "UnknownQuery";
@ -62,19 +42,24 @@ void QueryClient::preInitialize() {
DatabaseHelper::assignDatabaseId(this->sql, this->getServerId(), _this.lock());
if(ts::config::query::sslMode == 0) {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
}
/* may already calls handle_connection_initialized() */
if(!this->connection->initialize(error))
return false;
return true;
}
void QueryClient::postInitialize() {
lock_guard<recursive_mutex> lock(this->lock_packet_handle); /* we dont want to handle anything while we're initializing */
QueryClient::~QueryClient() {
memtrack::freed<QueryClient>(this);
delete this->connection;
}
void QueryClient::handle_connection_initialized() {
std::lock_guard lock{this->command_lock}; /* we dont want to handle anything while we're initializing */
this->connectTimestamp = system_clock::now();
this->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(this->connectTimestamp.time_since_epoch()).count();
if(ts::config::query::sslMode == 1 && this->connectionType != ConnectionType::SSL_ENCRIPTED) {
if(ts::config::query::sslMode == 1 && this->connection->connection_type() != server::query::ConnectionType::SSL_ENCRYPTED) {
command_result error{error::failed_connection_initialisation, "Please use a SSL encryption!"};
this->notifyError(error);
error.release_details();
@ -104,7 +89,7 @@ void QueryClient::postInitialize() {
}
if(!this->whitelisted) {
threads::MutexLock lock(this->handle->loginLock);
threads::MutexLock llock(this->handle->loginLock);
if(this->handle->queryBann.count(this->getPeerIp()) > 0) {
auto ban = this->handle->queryBann[this->getPeerIp()];
Command cmd("error");
@ -120,12 +105,26 @@ void QueryClient::postInitialize() {
this->update_cached_permissions();
}
void QueryClient::writeMessage(const std::string& message) {
if(this->state == ConnectionState::DISCONNECTED || !this->handle) return;
void QueryClient::handle_connection_finalized() {
/* when this has been called there could not be any command executing! */
//TODO: Is this statement really true?
if(this->connectionType == ConnectionType::PLAIN) this->writeRawMessage(message);
else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) this->ssl_handler.send(pipes::buffer_view{(void*) message.data(), message.length()});
else logCritical(LOG_GENERAL, "Invalid query connection type to write to!");
if(this->server) {
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
if(this->handle)
this->handle->unregisterConnection(dynamic_pointer_cast<QueryClient>(_this.lock()));
}
void QueryClient::writeMessage(const std::string& message) {
if(this->state == ClientState::DISCONNECTED) return;
this->connection->send_data(message);
}
@ -142,331 +141,25 @@ bool QueryClient::close_connection(const std::chrono::system_clock::time_point&
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
if(!ownLock) return false;
unique_lock<std::recursive_mutex> handleLock(this->lock_packet_handle);
unique_lock<threads::Mutex> lock(this->closeLock);
bool flushing = flushTimeout.time_since_epoch().count() != 0;
if(this->state == ConnectionState::DISCONNECTED || (flushing && this->state == ConnectionState::DISCONNECTING)) return false;
this->state = flushing ? ConnectionState::DISCONNECTING : ConnectionState::DISCONNECTED;
if(this->readEvent) { //Attention dont trigger this within the read thread!
event_del_block(this->readEvent);
event_free(this->readEvent);
this->readEvent = nullptr;
}
if(this->server){
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
if(flushing){
this->flushThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [ownLock, flushTimeout](){
while(ownLock->state == ConnectionState::DISCONNECTING && flushTimeout > system_clock::now()){
{
std::lock_guard buffer_lock(ownLock->buffer_lock);
if(ownLock->readQueue.empty() && ownLock->writeQueue.empty()) break;
}
usleep(10 * 1000);
}
if(ownLock->state == ConnectionState::DISCONNECTING) ownLock->disconnectFinal();
});
flushThread->name("Flush thread QC").execute();
} else {
threads::MutexLock l1(this->flushThreadLock);
handleLock.unlock();
lock.unlock();
if(this->flushThread){
threads::NegatedMutexLock l(this->closeLock);
this->flushThread->join();
}
disconnectFinal();
}
this->connection->close_connection(flushTimeout);
return true;
}
void QueryClient::disconnectFinal() {
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
lock_guard<recursive_mutex> lock_handle(this->lock_packet_handle);
threads::MutexLock lock_close(this->closeLock);
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
bool QueryClient::process_next_command() {
using CommandAssembleState = server::query::CommandAssembleState;
if(final_disconnected) {
logError(LOG_QUERY, "Tried to disconnect a client twice!");
return;
}
final_disconnected = true;
this->state = ConnectionState::DISCONNECTED;
{
threads::MutexTryLock l(this->flushThreadLock);
if(!!l) {
if(this->flushThread) {
this->flushThread->detach();
delete this->flushThread; //Release the captured this lock
this->flushThread = nullptr;
}
}
}
lock_guard clock(this->command_lock);
if(!this->handle || this->state == ClientState::DISCONNECTED) return false;
if(this->writeEvent) {
event_del_block(this->writeEvent);
event_free(this->writeEvent);
this->writeEvent = nullptr;
}
if(this->readEvent) {
event_del_block(this->readEvent);
event_free(this->readEvent);
this->readEvent = nullptr;
}
if(this->clientFd > 0) {
if(shutdown(this->clientFd, SHUT_RDWR) < 0)
debugMessage(LOG_QUERY, "Could not shutdown query client socket! {} ({})", errno, strerror(errno));
if(close(this->clientFd) < 0)
debugMessage(LOG_QUERY, "Failed to close the query client socket! {} ({})", errno, strerror(errno));
this->clientFd = -1;
}
if(this->server) {
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
this->readQueue.clear();
this->writeQueue.clear();
if(this->handle)
this->handle->unregisterConnection(dynamic_pointer_cast<QueryClient>(_this.lock()));
}
void QueryClient::writeRawMessage(const std::string &message) {
{
std::lock_guard lock(this->buffer_lock);
this->writeQueue.push_back(message);
}
if(this->writeEvent) event_add(this->writeEvent, nullptr);
}
void QueryClient::handleMessageWrite(int fd, short, void *) {
auto ownLock = _this.lock();
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
if(this->state == ConnectionState::DISCONNECTED) return;
if(!buffer_lock.owns_lock()) {
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
return;
}
int writes = 0;
string buffer;
while(writes < 10 && !this->writeQueue.empty()) {
if(buffer.empty()) {
buffer = std::move(this->writeQueue.front());
this->writeQueue.pop_front();
}
auto length = send(fd, buffer.data(), buffer.length(), MSG_NOSIGNAL);
#ifdef DEBUG_TRAFFIC
debugMessage("Write " + to_string(buffer.length()));
hexDump((void *) buffer.data(), buffer.length());
#endif
if(length == -1) {
if (errno == EINTR || errno == EAGAIN) {
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
return;
}
else {
logError(LOG_QUERY, "{} Failed to write message: {} ({} => {})", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
threads::Thread([=](){ ownLock->close_connection(chrono::system_clock::now() + chrono::seconds{5}); }).detach();
return;
}
} else {
if(buffer.length() == length)
buffer = "";
else
buffer = buffer.substr(length);
}
writes++;
}
if(!buffer.empty())
this->writeQueue.push_front(buffer);
if(!this->writeQueue.empty() && this->writeEvent)
event_add(this->writeEvent, nullptr);
}
void QueryClient::handleMessageRead(int fd, short, void *) {
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
if(!ownLock) {
logCritical(LOG_QUERY, "Could not get own lock!");
return;
}
string buffer(1024, 0);
auto length = read(fd, (void*) buffer.data(), buffer.length());
if(length <= 0){
if(errno == EINTR || errno == EAGAIN)
;//event_add(this->readEvent, nullptr);
else if(length == 0 && errno == 0) {
logMessage(LOG_QUERY, "{} Connection closed. Client disconnected.", CLIENT_STR_LOG_PREFIX);
event_del_noblock(this->readEvent);
std::thread([ownLock]{
ownLock->close_connection();
}).detach();
} else {
logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
event_del_noblock(this->readEvent);
threads::Thread(THREAD_SAVE_OPERATIONS, [ownLock](){ ownLock->close_connection(); }).detach();
}
return;
}
buffer.resize(length);
{
std::lock_guard buffer_lock(this->buffer_lock);
if(this->state == ConnectionState::DISCONNECTED)
return;
this->readQueue.push_back(std::move(buffer));
#ifdef DEBUG_TRAFFIC
debugMessage("Read " + to_string(buffer.length()));
hexDump((void *) buffer.data(), buffer.length());
#endif
}
if(this->handle)
this->handle->executePool()->execute([ownLock]() {
int counter = 0;
while(ownLock->tickIOMessageProgress() && counter++ < 15);
});
}
bool QueryClient::tickIOMessageProgress() {
lock_guard<recursive_mutex> lock(this->lock_packet_handle);
if(!this->handle || this->state == ConnectionState::DISCONNECTED || this->state == ConnectionState::DISCONNECTING) return false;
string message;
bool next = false;
{
std::lock_guard buffer_lock(this->buffer_lock);
if(this->readQueue.empty()) return false;
message = std::move(this->readQueue.front());
this->readQueue.pop_front();
next |= this->readQueue.empty();
}
if(this->connectionType == ConnectionType::PLAIN) {
int count = 0;
while(this->handleMessage(pipes::buffer_view{(void*) message.data(), message.length()}) && count++ < 15) message = "";
next |= count == 15;
} else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) {
this->ssl_handler.process_incoming_data(pipes::buffer_view{(void*) message.data(), message.length()});
} else if(this->connectionType == ConnectionType::UNKNOWN) {
if(config::query::sslMode != 0 && pipes::SSL::isSSLHeader(message)) {
this->initializeSSL();
/*
* - Content
* \x16
* -Version (1)
* \x03 \x00
* - length (2)
* \x00 \x04
*
* - Header
* \x00 -> hello request (3)
* \x05 -> length (4)
*/
//this->writeRawMessage(string("\x16\x03\x01\x00\x05\x00\x00\x00\x00\x00", 10));
} else {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
}
next = true;
{
std::lock_guard buffer_lock(this->buffer_lock);
this->readQueue.push_front(std::move(message));
}
}
return next;
}
extern InstanceHandler* serverInstance;
void QueryClient::initializeSSL() {
this->connectionType = ConnectionType::SSL_ENCRIPTED;
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
this->ssl_handler.callback_data(std::bind(&QueryClient::handleMessage, this, placeholders::_1));
this->ssl_handler.callback_write(std::bind(&QueryClient::writeRawMessage, this, placeholders::_1));
this->ssl_handler.callback_initialized = std::bind(&QueryClient::postInitialize, this);
this->ssl_handler.callback_error([&](int code, const std::string& message) {
if(code == PERROR_SSL_ACCEPT) {
this->disconnect("invalid accept");
} else if(code == PERROR_SSL_TIMEOUT)
this->disconnect("invalid accept (timeout)");
else
logError(LOG_QUERY, "Got unknown ssl error ({} | {})", code, message);
});
{
auto context = serverInstance->sslManager()->getQueryContext();
auto options = make_shared<pipes::SSL::Options>();
options->type = pipes::SSL::SERVER;
options->context_method = TLS_method();
options->default_keypair({context->privateKey, context->certificate});
if(!this->ssl_handler.initialize(options)) {
logError(LOG_QUERY, "[{}] Failed to setup ssl!", CLIENT_STR_LOG_PREFIX);
}
}
}
bool QueryClient::handleMessage(const pipes::buffer_view& message) {
{
threads::MutexLock l(this->closeLock);
if(this->state == ConnectionState::DISCONNECTED)
return false;
}
#ifdef DEBUG_TRAFFIC
debugMessage("Handling message " + to_string(message.length()));
hexDump((void *) message.data(), message.length());
#endif
string command;
{
this->lineBuffer += message.string();
int length = 2;
auto pos = this->lineBuffer.find("\r\n");
if(pos == string::npos) pos = this->lineBuffer.find("\n\r");
if(pos == string::npos) {
length = 1;
pos = this->lineBuffer.find('\n');
}
if(pos != string::npos){
command = this->lineBuffer.substr(0, pos);
if(this->lineBuffer.size() > pos + length)
this->lineBuffer = this->lineBuffer.substr(pos + length);
else
this->lineBuffer.clear();
}
if(pos == string::npos) return false;
string command{};
bool more_pending{false};
switch (this->connection->next_command(command)) {
case CommandAssembleState::MORE_COMMANDS_PENDING:
more_pending = true;
case CommandAssembleState::SUCCESS:
break;
case CommandAssembleState::NO_COMMAND_PENDING:
return false; /* nothing to do */
}
if(command.empty() || command.find_first_not_of(' ') == string::npos) { //Empty command
@ -515,7 +208,7 @@ bool QueryClient::handleMessage(const pipes::buffer_view& message) {
error = command_result{error::vs_critical, std::string{ex.what()}};
goto handle_error;
}
return true;
return more_pending;
handle_error:
this->notifyError(error);
@ -539,15 +232,13 @@ void QueryClient::tick(const std::chrono::system_clock::time_point &time) {
}
void QueryClient::queryTick() {
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
if(this->idleTimestamp.time_since_epoch().count() > 0 && system_clock::now() - this->idleTimestamp > minutes(5)){
debugMessage(LOG_QUERY, "Dropping client " + this->getLoggingPeerIp() + "|" + this->getDisplayName() + ". (Timeout)");
this->close_connection(system_clock::now() + seconds(1));
}
if(this->connectionType == ConnectionType::UNKNOWN && system_clock::now() - milliseconds(500) > connectedTimestamp) {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
if(this->connection->connection_type() == server::query::ConnectionType::UNKNOWN && system_clock::now() - milliseconds{500} > connectedTimestamp) {
this->connection->enforce_text_connection();
}
}

View File

@ -10,19 +10,18 @@ namespace ts::server {
class QueryServer;
class QueryAccount;
namespace server::query {
class QueryClientConnection;
}
class QueryClient : public ConnectedClient {
friend class QueryServer;
enum ConnectionType {
PLAIN,
SSL_ENCRIPTED,
UNKNOWN
};
friend class server::query::QueryClientConnection;
public:
QueryClient(QueryServer*, int sockfd);
~QueryClient() override;
[[nodiscard]] inline QueryServer* getQueryServer() { return this->handle; }
void writeMessage(const std::string&);
void sendCommand(const ts::Command &command, bool low = false) override;
@ -30,7 +29,6 @@ namespace ts::server {
bool disconnect(const std::string &reason) override;
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
void disconnectFinal();
bool eventActive(QueryEventGroup, QueryEventSpecifier);
void toggleEvent(QueryEventGroup, QueryEventSpecifier, bool);
@ -41,51 +39,26 @@ namespace ts::server {
inline std::shared_ptr<QueryAccount> getQueryAccount() { return this->query_account; }
protected:
void preInitialize();
void postInitialize();
/* Will be called as soon the connection has been initialized. This means directly within the initialize call or within the IO read callback. */
void handle_connection_initialized();
void handle_connection_finalized();
void tick(const std::chrono::system_clock::time_point &time) override;
void queryTick();
protected:
void initializeSSL();
/* returns true if more commands are pending */
bool process_next_command();
bool handleMessage(const pipes::buffer_view&);
bool tickIOMessageProgress();
void handleMessageRead(int, short, void*);
void handleMessageWrite(int, short, void*);
void writeRawMessage(const std::string&);
void applySelfLock(const std::shared_ptr<QueryClient> &cl);
[[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr<QueryClient> &cl);
private:
QueryServer* handle;
ConnectionType connectionType = ConnectionType::UNKNOWN;
server::query::QueryClientConnection* connection{nullptr};
bool whitelisted = false;
int clientFd = -1;
::event* readEvent = nullptr;
::event* writeEvent = nullptr;
threads::Mutex closeLock;
pipes::SSL ssl_handler;
std::mutex buffer_lock;
std::deque<std::string> writeQueue;
std::deque<std::string> readQueue;
threads::Mutex flushThreadLock;
threads::Thread* flushThread = nullptr;
bool final_disconnected = false;
std::string lineBuffer;
std::chrono::time_point<std::chrono::system_clock> connectedTimestamp;
uint16_t eventMask[QueryEventGroup::QEVENTGROUP_MAX];
std::recursive_mutex lock_packet_handle;
std::recursive_mutex lock_query_tick;
std::shared_ptr<QueryAccount> query_account;
protected:
command_result handleCommand(Command &command) override;
@ -139,9 +112,15 @@ namespace ts::server {
bool notifyClientLeftView(const std::deque<std::shared_ptr<ConnectedClient>> &deque, const std::string &string, bool b, const ViewReasonServerLeftT &t) override;
bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, const std::shared_ptr<BasicChannel> &target_channel, const std::string &message, std::shared_ptr<ConnectedClient> invoker, bool lock_channel_tree) override;
bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree,
const std::shared_ptr<BasicChannel> &target_channel) override;
bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, const std::string &message, std::shared_ptr<ConnectedClient> invoker, size_t length, bool lock_channel_tree) override;
bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message) override;
private:
command_result handleCommandExit(Command&);

View File

@ -0,0 +1,384 @@
//
// Created by WolverinDEV on 11/03/2020.
//
#include "./QueryClientConnection.h"
#include <netinet/tcp.h>
#include <log/LogUtils.h>
#include <pipes/errors.h>
#include <src/InstanceHandler.h>
#include "./QueryClient.h"
#include "../ConnectedClient.h"
#include "../../server/QueryServer.h"
#include "QueryClientConnection.h"
using namespace ts::server::server::query;
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
#endif
namespace ts::server::server::query {
/* will be set by the event loop */
thread_local bool thread_is_event_loop{false};
}
QueryClientConnection::QueryClientConnection(ts::server::QueryClient *client, int fd) : client_handle{client}, file_descriptor_{fd} {
TAILQ_INIT(&this->write_queue);
}
QueryClientConnection::~QueryClientConnection() {
this->finalize(true);
}
bool QueryClientConnection::initialize(std::string &error) {
assert(this->client_handle);
int enabled{1};
int disabled{0};
setsockopt(this->file_descriptor_, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
if(setsockopt(this->file_descriptor_, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0)
logError(LOG_QUERY, "Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this->client_handle), errno, strerror(errno));
if(setsockopt(this->file_descriptor_, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0)
logError(LOG_QUERY, "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this->client_handle), errno, strerror(errno));
auto query_server = this->client_handle->getQueryServer();
this->readEvent = event_new(query_server->io_event_loop(), this->file_descriptor_, EV_READ | EV_PERSIST, [](int a1, short a2, void* _this) {
reinterpret_cast<QueryClientConnection*>(_this)->handle_event_read(a1, a2);
}, this);
this->writeEvent = event_new(query_server->io_event_loop(), this->file_descriptor_, EV_WRITE, [](int a1, short a2, void* _this){
reinterpret_cast<QueryClientConnection*>(_this)->handle_event_write(a1, a2);
}, this);
this->connection_state = ConnectionState::INITIALIZING;
if(ts::config::query::sslMode == 0) {
this->connection_state = ConnectionState::CONNECTED;
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
}
return true;
}
void QueryClientConnection::add_read_event() {
std::lock_guard elock{this->event_mutex};
if(this->readEvent) event_add(this->readEvent, nullptr);
}
void QueryClientConnection::finalize(bool is_destructor_call) {
auto old_state = this->connection_state;
this->connection_state = ConnectionState::DISCONNECTED;
/* unregister event handling */
{
std::unique_lock elock{this->event_mutex};
auto wevent = std::exchange(this->writeEvent, nullptr);
auto revent = std::exchange(this->readEvent, nullptr);
elock.unlock();
if(revent) {
if(thread_is_event_loop)
event_del_noblock(revent);
else
event_del_block(revent); /* may calls finalize() while we're waiting. But thats okey. */
event_free(revent);
}
if(wevent) {
if(thread_is_event_loop)
event_del_noblock(wevent);
else
event_del_block(wevent); /* may calls finalize() while we're waiting. But thats okey. */
event_free(wevent);
}
}
{
std::lock_guard block{this->buffer_lock};
/* Free the entire tail queue. */
while (auto buffer = TAILQ_FIRST(&this->write_queue)) {
TAILQ_REMOVE(&this->write_queue, buffer, tq);
free(buffer->original_ptr);
delete buffer;
}
TAILQ_INIT(&this->write_queue); /* just ensures a valid tailq */
::free(this->read_buffer.buffer);
this->read_buffer.buffer = nullptr;
this->read_buffer.length = 0;
this->read_buffer.fill_count = 0;
}
if(!is_destructor_call && old_state != ConnectionState::DISCONNECTED)
this->client_handle->handle_connection_finalized();
}
void QueryClientConnection::handle_event_read(int fd, short events) {
constexpr auto buffer_length{1024 * 4};
uint8_t buffer[buffer_length];
auto length = read(fd, (void *) buffer, buffer_length);
if (length <= 0) {
if (errno == EINTR || errno == EAGAIN)
return;
else if (length == 0) {
logMessage(LOG_QUERY, "{} Connection closed (r). Client disconnected.",
CLIENT_STR_LOG_PREFIX_(this->client_handle));
} else {
logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}",
CLIENT_STR_LOG_PREFIX_(this->client_handle), length, errno, strerror(errno));
}
event_del_noblock(this->readEvent);
this->close_connection(std::chrono::system_clock::time_point{});
return;
}
if (this->connection_type_ == ConnectionType::PLAIN_TEXT) {
plain_text_buffer_insert:
this->handle_decoded_message(buffer, length);
} else if (this->connection_type_ == ConnectionType::SSL_ENCRYPTED) {
ssl_buffer_insert:;
this->ssl_handler.process_incoming_data(pipes::buffer_view{(const char*) buffer, (size_t) length});;
} else {
if (config::query::sslMode != 0 && pipes::SSL::isSSLHeader(std::string{(const char *) buffer, (size_t) length})) {
if(!this->initialize_ssl()) return;
/*
* - Content
* \x16
* -Version (1)
* \x03 \x00
* - length (2)
* \x00 \x04
*
* - Header
* \x00 -> hello request (3)
* \x05 -> length (4)
*/
//this->writeRawMessage(string("\x16\x03\x01\x00\x05\x00\x00\x00\x00\x00", 10));
goto ssl_buffer_insert;
} else {
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
goto plain_text_buffer_insert;
}
}
}
void QueryClientConnection::handle_event_write(int fd, short events) {
bool readd_write{false};
if(events & EV_WRITE) {
/* Safe to access, because we're only reading the queue and the head could never change. Only within the IO loop itself. */
WriteBuffer* wbuffer;
while((wbuffer = TAILQ_FIRST(&this->write_queue))) {
auto written = send(fd, wbuffer->ptr, wbuffer->length, 0);
if(written <= 0) {
if(errno == EAGAIN) {
readd_write = true;
break;
}
if(written == 0) {
logMessage(LOG_QUERY, "{} Connection closed (w). Client disconnected.", CLIENT_STR_LOG_PREFIX_(this->client_handle));
} else {
logError(LOG_QUERY, "{} Failed to write! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX_(this->client_handle), written, errno, strerror(errno));
}
event_del_noblock(this->readEvent);
this->close_connection(std::chrono::system_clock::time_point{});
return;
}
wbuffer->length -= written;
if(wbuffer->length == 0) {
std::lock_guard block{this->buffer_lock};
TAILQ_REMOVE(&this->write_queue, wbuffer, tq);
::free(wbuffer->original_ptr);
delete wbuffer;
} else {
wbuffer->ptr += written;
}
}
}
if(this->connection_state == ConnectionState::DISCONNECTING) {
if(!readd_write || (events & EV_TIMEOUT)) {
/* disconnect timeouted or nothing more to write */
this->finalize(false);
return;
} else /* if(readd_write) */ { /* check not needed because tested before already */
auto time_left = this->disconnect_timeout - std::chrono::system_clock::now();
timeval timeout{0, 1};
if(time_left.count() > 0) {
timeout.tv_sec = std::chrono::floor<std::chrono::seconds>(time_left).count();
timeout.tv_usec = std::chrono::floor<std::chrono::microseconds>(time_left).count() % 1000000ULL;
}
event_add(this->writeEvent, &timeout);
}
} else if(readd_write) {
event_add(this->writeEvent, nullptr);
}
}
bool QueryClientConnection::initialize_ssl() {
this->connection_type_ = ConnectionType::SSL_ENCRYPTED;
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
this->ssl_handler.callback_data([&](const pipes::buffer_view &buffer) {
this->handle_decoded_message(buffer.data_ptr<void>(), buffer.length());
});
this->ssl_handler.callback_write([&](const pipes::buffer_view &buffer) {
this->send_data_raw({buffer.data_ptr<char>(), buffer.length()});
});
this->ssl_handler.callback_initialized = [&] {
this->client_handle->handle_connection_initialized();
};
this->ssl_handler.callback_error([&](int code, const std::string& message) {
if(code == PERROR_SSL_ACCEPT) {
logError(LOG_QUERY, "{} Failed to initialize query ssl session ({})", CLIENT_STR_LOG_PREFIX_(this->client_handle), message);
this->close_connection(std::chrono::system_clock::time_point{});
} else if(code == PERROR_SSL_TIMEOUT) {
logError(LOG_QUERY, "{} Failed to initialize query ssl session (timeout: {})", CLIENT_STR_LOG_PREFIX_(this->client_handle), message);
this->close_connection(std::chrono::system_clock::time_point{});
} else
logError(LOG_QUERY, "{} Received SSL error ({} | {})", CLIENT_STR_LOG_PREFIX_(this->client_handle), code, message);
});
{
auto context = serverInstance->sslManager()->getQueryContext();
auto options = std::make_shared<pipes::SSL::Options>();
options->type = pipes::SSL::SERVER;
options->context_method = TLS_method();
options->default_keypair({context->privateKey, context->certificate});
if(!this->ssl_handler.initialize(options)) {
logError(LOG_QUERY, "[{}] Failed to setup ssl!", CLIENT_STR_LOG_PREFIX_(this->client_handle));
this->close_connection(std::chrono::system_clock::time_point{});
return false;
}
}
return true;
}
void QueryClientConnection::handle_decoded_message(const void *buffer, size_t size) {
{
std::lock_guard block{this->buffer_lock};
if((this->read_buffer.length - this->read_buffer.fill_count) < size) { /* !this->read_buffer.buffer is already implicitly implemented because by default read_buffer.length will be zero */
const auto new_size{this->read_buffer.length + size + 128};
auto new_buffer = ::malloc(new_size);
assert(new_buffer);
if(this->read_buffer.fill_count) memcpy(new_buffer, this->read_buffer.buffer, this->read_buffer.fill_count);
::free(this->read_buffer.buffer);
this->read_buffer.buffer = new_buffer;
this->read_buffer.length = new_size;
}
assert(this->read_buffer.buffer);
assert(this->read_buffer.length - this->read_buffer.fill_count >= size);
memcpy((char*) this->read_buffer.buffer + this->read_buffer.fill_count, buffer, size);
this->read_buffer.fill_count += size;
}
{
//TODO: Improve this command progress
auto qserver{this->client_handle->handle};
if(qserver) {
auto wlock{this->client_handle->_this};
qserver->executePool()->execute([wlock]() {
auto client{std::dynamic_pointer_cast<QueryClient>(wlock.lock())};
if(!client) return;
int counter = 0;
while(client->process_next_command() && counter++ < 15);
});
}
}
}
void QueryClientConnection::send_data(const std::string_view &buffer) {
if(this->connection_type_ == ConnectionType::PLAIN_TEXT)
this->send_data_raw(buffer);
else if(this->connection_type_ == ConnectionType::SSL_ENCRYPTED)
this->ssl_handler.send(pipes::buffer_view{buffer.data(), buffer.length()});
}
void QueryClientConnection::send_data_raw(const std::string_view &buffer) {
auto wbuf = new WriteBuffer{};
wbuf->original_ptr = (char*) malloc(buffer.length());
wbuf->ptr = wbuf->original_ptr;
memcpy(wbuf->ptr, buffer.data(), buffer.length());
wbuf->length = buffer.length();
{
std::lock_guard wlock{this->buffer_lock};
TAILQ_INSERT_TAIL(&this->write_queue, wbuf, tq);
}
{
std::lock_guard elock{this->event_mutex};
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
}
}
void QueryClientConnection::close_connection(const std::chrono::system_clock::time_point &timeout) {
if(timeout.time_since_epoch().count() > 0) {
this->connection_state = ConnectionState::DISCONNECTING;
this->disconnect_timeout = timeout;
std::lock_guard elock{this->event_mutex};
if(this->writeEvent) {
event_add(this->writeEvent, nullptr);
return;
}
/* failed to add the write event, so call disconnect */
}
if(this->connection_state == ConnectionState::DISCONNECTED) return;
this->finalize(false);
}
void QueryClientConnection::enforce_text_connection() {
if(this->connection_state != ConnectionState::INITIALIZING) return;
this->connection_state = ConnectionState::CONNECTED;
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
}
CommandAssembleState QueryClientConnection::next_command(std::string &result) {
std::lock_guard block{this->buffer_lock};
auto new_line_idx = (char*) memchr(this->read_buffer.buffer, '\n', this->read_buffer.fill_count);
if(!new_line_idx) return CommandAssembleState::NO_COMMAND_PENDING;
const auto length = ((char*) this->read_buffer.buffer - new_line_idx) * sizeof(*new_line_idx);
auto line_length{length};
if(length > 0 && *(new_line_idx - 1) == '\r')
line_length--;
result.assign((char*) this->read_buffer.buffer, line_length);
//Do not copy the \r character
auto copy_bytes{this->read_buffer.fill_count - length};
if(copy_bytes > 0 && *(new_line_idx + 1) == '\r') {
copy_bytes--;
new_line_idx++;
}
memcpy(this->read_buffer.buffer, new_line_idx + 1, copy_bytes);
this->read_buffer.fill_count = copy_bytes;
return copy_bytes == 0 ? CommandAssembleState::SUCCESS : CommandAssembleState::MORE_COMMANDS_PENDING;
}

View File

@ -0,0 +1,98 @@
#pragma once
#include <chrono>
#include <event.h>
#include <deque>
#include <string>
#include <pipes/ssl.h>
#include <sys/queue.h>
namespace ts::server {
class QueryClient;
}
namespace ts::server::server::query {
enum struct ConnectionType {
UNKNOWN,
PLAIN_TEXT,
SSL_ENCRYPTED,
/* SSH */
};
enum struct ConnectionState {
INITIALIZING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
enum struct CommandAssembleState {
SUCCESS,
MORE_COMMANDS_PENDING,
NO_COMMAND_PENDING
};
class QueryClientConnection {
public:
explicit QueryClientConnection(QueryClient* /* client */, int /* file descriptor */);
~QueryClientConnection();
[[nodiscard]] inline ConnectionType connection_type() const { return this->connection_type_; }
bool initialize(std::string& /* error */);
void add_read_event();
void finalize(bool /* is destructor call */);
void send_data(const std::string_view& /* payload */);
void send_data_raw(const std::string_view& /* payload */);
void enforce_text_connection();
[[nodiscard]] CommandAssembleState next_command(std::string& /* command */);
/* could be called from every thread (event IO thread) */
void close_connection(const std::chrono::system_clock::time_point& /* disconnect timeout */);
private:
struct WriteBuffer {
char* original_ptr;
char* ptr;
size_t length;
TAILQ_ENTRY(WriteBuffer) tq;
};
QueryClient* client_handle{nullptr};
ConnectionState connection_state{ConnectionState::INITIALIZING};
std::chrono::system_clock::time_point disconnect_timeout{};
ConnectionType connection_type_{ConnectionType::UNKNOWN};
int file_descriptor_{-1};
/* only delete the events within the event loop! */
std::mutex event_mutex{};
::event* readEvent{nullptr};
::event* writeEvent{nullptr};
pipes::SSL ssl_handler{};
std::mutex buffer_lock{};
struct {
void* buffer{nullptr};
size_t length{0};
size_t fill_count{0};
std::chrono::system_clock::time_point last_shrink{};
} read_buffer;
TAILQ_HEAD(, WriteBuffer) write_queue{};
void handle_event_write(int, short);
void handle_event_read(int, short);
bool initialize_ssl();
void handle_decoded_message(const void* /* message */, size_t /* length */);
};
}

View File

@ -175,7 +175,10 @@ bool QueryClient::notifyClientLeftView(const std::deque<std::shared_ptr<Connecte
return ConnectedClient::notifyClientLeftView(clients, string, b, t);
}
bool QueryClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, const std::shared_ptr<BasicChannel> &target_channel, const std::string &message, std::shared_ptr<ConnectedClient> invoker, bool lock_channel_tree) {
bool QueryClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree,
const std::shared_ptr<BasicChannel> &target_channel) {
if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) {
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
@ -187,10 +190,13 @@ bool QueryClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClie
}), this->visibleClients.end());
return true;
}
return ConnectedClient::notifyClientLeftViewKicked(client, target_channel, message, invoker, lock_channel_tree);
return ConnectedClient::notifyClientLeftViewKicked(client, channel_from, message, invoker, lock_channel_tree, target_channel);
}
bool QueryClient::notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, const std::string &message, std::shared_ptr<ConnectedClient> invoker, size_t length, bool lock_channel_tree) {
bool QueryClient::notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message) {
if(!this->eventActive(QueryEventGroup::QEVENTGROUP_CLIENT_VIEW, QueryEventSpecifier::QEVENTSPECIFIER_CLIENT_VIEW_LEAVE)) {
this->visibleClients.erase(std::remove_if(this->visibleClients.begin(), this->visibleClients.end(), [&, client](const weak_ptr<ConnectedClient>& weak) {
auto c = weak.lock();
@ -202,7 +208,7 @@ bool QueryClient::notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClie
}), this->visibleClients.end());
return true;
}
return ConnectedClient::notifyClientLeftViewBanned(client, message, invoker, length, lock_channel_tree);
return ConnectedClient::notifyClientLeftViewBanned(client, channel_from, invoker, length, lock_channel_tree, message);
}
bool QueryClient::notifyMusicQueueAdd(const std::shared_ptr<MusicClient> &bot, const std::shared_ptr<ts::music::SongInfo> &entry, int index, const std::shared_ptr<ConnectedClient> &invoker) {

View File

@ -0,0 +1,300 @@
//
// Created by WolverinDEV on 10/03/2020.
//
#include "PacketDecoder.h"
#include <protocol/buffers.h>
#include <protocol/AcknowledgeManager.h>
#include <protocol/CompressionHandler.h>
#include <protocol/CryptHandler.h>
#include "../../ConnectionStatistics.h"
using namespace ts;
using namespace ts::protocol;
using namespace ts::connection;
using namespace ts::server::server::udp;
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
: crypt_handler_{crypt_handler} {
memtrack::allocated<PacketDecoder>(this);
}
PacketDecoder::~PacketDecoder() {
memtrack::freed<PacketDecoder>(this);
this->reset();
}
void PacketDecoder::reset() {
std::lock_guard buffer_lock(this->packet_buffer_lock);
for(auto& buffer : this->_command_fragment_buffers)
buffer.reset();
}
bool PacketDecoder::decode_incoming_data(const pipes::buffer_view &buffer) {
std::string error{};
bool needs_command_reassemble{false};
auto result = this->decode_incoming_data_(error, needs_command_reassemble, buffer);
if(result != PacketDecodeResult::SUCCESS)
if(auto callback{this->callback_decode_failed}; callback)
callback(this->callback_argument, result, error);
return needs_command_reassemble;
}
PacketDecodeResult PacketDecoder::decode_incoming_data_(std::string& error, bool& commands_pending, const pipes::buffer_view &buffer) {
#ifdef FUZZING_TESTING_INCOMMING
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
if (this->client->state == ConnectionState::CONNECTED) {
#endif
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
return;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
}
#endif
#endif
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid())
return PacketDecodeResult::INVALID_PACKET;
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()));
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) {
/* handle the order stuff */
auto& fragment_buffer = this->_command_fragment_buffers[PacketDecoder::command_fragment_buffer_index(packet_parser.type())];
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
auto result = fragment_buffer.accept_index(packet_parser.packet_id());
if(result != 0) { /* packet index is ahead buffer index */
error = "pid: " + std::to_string(packet_parser.packet_id()) + ",";
error += "bidx: " + std::to_string(fragment_buffer.current_index()) + ",";
error += "bcap: " + std::to_string(fragment_buffer.capacity());
if(result == -1) { /* underflow */
/* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
return PacketDecodeResult::DUPLICATED_PACKET;
}
return PacketDecodeResult::BUFFER_OVERFLOW;
}
}
//NOTICE I found out that the Compressed flag is set if the packet contains an audio header
/* decrypt the packet if needed */
if(packet_parser.is_encrypted()) {
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
bool use_default_key{!this->protocol_encrypted}, decrypt_result;
decrypt_packet:
if(use_default_key) {
crypt_key = CryptHandler::default_key;
crypt_nonce = CryptHandler::default_nonce;
} else {
if(!this->crypt_handler_->generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce))
return PacketDecodeResult::DECRYPT_KEY_GEN_FAILED;
}
decrypt_result = this->crypt_handler_->decrypt(
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
data,
crypt_key, crypt_nonce,
error
);
if(!decrypt_result) {
if(packet_parser.packet_id() < 10 && packet_parser.estimated_generation() == 0) {
if(use_default_key) {
return PacketDecodeResult::DECRYPT_FAILED;
} else {
use_default_key = true;
goto decrypt_packet;
}
} else {
return PacketDecodeResult::DECRYPT_FAILED;
}
}
packet_parser.set_decrypted();
}
if(auto statistics{this->statistics_}; statistics)
statistics->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length());
#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
if(is_command) {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
CommandFragment fragment_entry{
packet_parser.packet_id(),
packet_parser.estimated_generation(),
packet_parser.flags(),
(uint32_t) packet_parser.payload_length(),
packet_parser.payload().own_buffer()
};
{
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
if(!fragment_buffer.insert_index(packet_parser.packet_id(), std::move(fragment_entry)))
return PacketDecodeResult::COMMAND_INSTERT_FAILED;
}
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
commands_pending = true;
} else {
this->callback_decoded_packet(this->callback_argument, packet_parser);
}
return PacketDecodeResult::SUCCESS;
}
bool PacketDecoder::verify_encryption(const pipes::buffer_view &buffer) {
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
return this->crypt_handler_->verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
}
CommandReassembleResult PacketDecoder::reassemble_command(pipes::buffer &result, bool &is_command_low) {
bool more_commands_pending{false};
command_fragment_buffer_t* buffer{nullptr};
std::unique_lock<std::recursive_timed_mutex> buffer_lock; /* general buffer lock */
{
//FIXME: Currently command low packets cant be handled if there is a command packet stuck in reassemble queue
/* handle commands before command low packets */
for(auto& buf : this->_command_fragment_buffers) {
std::unique_lock ring_lock(buf.buffer_lock, std::try_to_lock);
if(!ring_lock.owns_lock()) continue;
if(buf.front_set()) {
if(!buffer) { /* lets still test for reexecute */
buffer_lock = move(ring_lock);
buffer = &buf;
} else {
more_commands_pending = true;
break;
}
}
}
}
if(!buffer)
return CommandReassembleResult::NO_COMMANDS_PENDING;
uint8_t packet_flags{0};
pipes::buffer payload{};
/* lets find out if we've to reassemble the packet */
auto& first_buffer = buffer->slot_value(0);
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
uint16_t sequence_length{1};
size_t total_payload_length{first_buffer.payload_length};
do {
if(sequence_length >= buffer->capacity())
return CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG;
if(!buffer->slot_set(sequence_length))
return CommandReassembleResult::NO_COMMANDS_PENDING; /* we need more packets */
auto& packet = buffer->slot_value(sequence_length++);
total_payload_length += packet.payload_length;
if(packet.packet_flags & PacketFlag::Fragmented) {
/* yep we find the end */
break;
}
} while(true);
/* ok we have all fragments lets reassemble */
/*
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
* We're checking it here because we dont want to make a huge allocation
*/
assert(total_payload_length < 512 * 1024 * 1024);
pipes::buffer packet_buffer{total_payload_length};
char* packet_buffer_ptr = &packet_buffer[0];
size_t packet_count{0};
packet_flags = buffer->slot_value(0).packet_flags;
while(packet_count < sequence_length) {
auto fragment = buffer->pop_front();
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
packet_buffer_ptr += fragment.payload_length;
packet_count++;
}
#ifndef _NDEBUG
if((packet_buffer_ptr - 1) != &packet_buffer[packet_buffer.length() - 1]) {
logCritical(0,
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
(void*) packet_buffer_ptr,
(void*) &packet_buffer[packet_buffer.length() - 1]
);
}
#endif
payload = packet_buffer;
} else {
auto packet = buffer->pop_front();
packet_flags = packet.packet_flags;
payload = packet.payload;
}
more_commands_pending |= buffer->front_set(); /* set the more flag if we have more to process */
buffer_lock.unlock();
if(packet_flags & PacketFlag::Compressed) {
std::string error{};
auto decompressed_size = compression::qlz_decompressed_size(payload.data_ptr(), payload.length());
auto uncompressed_buffer = buffer::allocate_buffer(decompressed_size);
if(!compression::qlz_decompress_payload(payload.data_ptr(), uncompressed_buffer.data_ptr(), &decompressed_size))
return CommandReassembleResult::COMMAND_DECOMPRESS_FAILED;
payload = uncompressed_buffer.range(0, decompressed_size);
}
result = std::move(payload);
return more_commands_pending ? CommandReassembleResult::MORE_COMMANDS_PENDING : CommandReassembleResult::SUCCESS;
}
void PacketDecoder::force_insert_command(const pipes::buffer_view &buffer) {
CommandFragment fragment_entry{
0,
0,
PacketFlag::Unencrypted,
(uint32_t) buffer.length(),
buffer.own_buffer()
};
{
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
fragment_buffer.push_front(std::move(fragment_entry));
}
}
void PacketDecoder::register_initiv_packet() {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
std::unique_lock buffer_lock(fragment_buffer.buffer_lock);
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
}

View File

@ -0,0 +1,120 @@
#pragma once
#include <misc/spin_lock.h>
#include <mutex>
#include <deque>
#include <protocol/Packet.h>
#include <protocol/generation.h>
#include <protocol/ringbuffer.h>
namespace ts::connection {
class CryptHandler;
class CompressionHandler;
class AcknowledgeManager;
}
namespace ts::stats {
class ConnectionStatistics;
}
namespace ts::server::server::udp {
struct CommandFragment {
uint16_t packet_id{0};
uint16_t packet_generation{0};
uint8_t packet_flags{0};
uint32_t payload_length : 24;
pipes::buffer payload{};
CommandFragment() { this->payload_length = 0; }
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload)
: packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags}, payload_length{payloadLength}, payload{std::move(payload)} {}
CommandFragment& operator=(const CommandFragment&) = default;
CommandFragment(const CommandFragment& other) = default;
CommandFragment(CommandFragment&&) = default;
};
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
enum struct PacketDecodeResult {
SUCCESS,
INVALID_PACKET,
DUPLICATED_PACKET, /* error message contains debug properties */
BUFFER_OVERFLOW, /* error message contains debug properties */
DECRYPT_KEY_GEN_FAILED,
DECRYPT_FAILED, /* has custom message */
COMMAND_INSTERT_FAILED
};
enum struct CommandReassembleResult {
SUCCESS,
MORE_COMMANDS_PENDING, /* equal with success */
NO_COMMANDS_PENDING,
COMMAND_TOO_LARGE, /* this is a fatal error to the connection */
COMMAND_DECOMPRESS_FAILED,
SEQUENCE_LENGTH_TOO_LONG /* unrecoverable error */
};
class PacketDecoder {
typedef protocol::PacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
public:
//typedef std::function<void(const protocol::ClientPacketParser&)> callback_decoded_packet_t;
//typedef std::function<void(uint16_t /* packet id */, bool /* is command low */)> callback_send_acknowledge_t;
//typedef std::function<void(PacketDecodeResult /* error */, const std::string& /* custom message */)> callback_decode_failed_t;
/* gets better optimized out */
typedef void(*callback_decoded_packet_t)(void* /* cb argument */, const protocol::ClientPacketParser&);
typedef void(*callback_send_acknowledge_t)(void* /* cb argument */, uint16_t /* packet id */, bool /* is command low */);
typedef void(*callback_decode_failed_t)(void* /* cb argument */, PacketDecodeResult /* error */, const std::string& /* custom message */);
explicit PacketDecoder(connection::CryptHandler* /* crypt handler */);
~PacketDecoder();
void reset();
bool verify_encryption(const pipes::buffer_view& /* full packet */);
/* true if commands might be pending */
bool decode_incoming_data(const pipes::buffer_view &/* buffer */);
/* This method is not thread save! Only one concurrent call supported */
CommandReassembleResult reassemble_command(pipes::buffer& /* result */, bool& /* is command low */);
void force_insert_command(const pipes::buffer_view& /* payload */);
void register_initiv_packet();
[[nodiscard]] inline std::shared_ptr<stats::ConnectionStatistics> get_statistics() { return this->statistics_; }
inline void set_statistics(const std::shared_ptr<stats::ConnectionStatistics>& stats) { this->statistics_ = stats; }
[[nodiscard]] inline bool is_protocol_encrypted() const { return this->protocol_encrypted; }
void set_protocol_encrypted(bool flag) { this->protocol_encrypted = flag; }
void* callback_argument{nullptr};
callback_decoded_packet_t callback_decoded_packet{[](auto, auto&){}}; /* needs to be valid all the time! */
callback_send_acknowledge_t callback_send_acknowledge{[](auto, auto, auto){}}; /* needs to be valid all the time! */
callback_decode_failed_t callback_decode_failed{nullptr};
private:
bool protocol_encrypted{false};
std::shared_ptr<stats::ConnectionStatistics> statistics_{nullptr};
connection::CryptHandler* crypt_handler_{nullptr};
std::array<protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
std::recursive_mutex packet_buffer_lock;
command_packet_reassembler _command_fragment_buffers;
static inline uint8_t command_fragment_buffer_index(uint8_t packet_index) {
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
}
PacketDecodeResult decode_incoming_data_(std::string& /* error */, bool& /* needs command reassemble */, const pipes::buffer_view &/* buffer */);
};
}

View File

@ -0,0 +1,230 @@
//
// Created by WolverinDEV on 09/03/2020.
//
#include "PacketEncoder.h"
#include <protocol/buffers.h>
#include <protocol/AcknowledgeManager.h>
#include <protocol/CompressionHandler.h>
#include <protocol/CryptHandler.cpp>
#include "../../ConnectionStatistics.h"
using namespace ts;
using namespace ts::server::server::udp;
PacketEncoder::PacketEncoder(ts::connection::CryptHandler *crypt_handler, ts::connection::CompressionHandler *compress_handler,
ts::connection::AcknowledgeManager *ack_handler) : crypt_handler_{crypt_handler}, compress_handler_{compress_handler}, acknowledge_handler_{ack_handler} {
memtrack::allocated<PacketEncoder>(this);
}
PacketEncoder::~PacketEncoder() {
memtrack::freed<PacketEncoder>(this);
this->reset();
}
void PacketEncoder::reset() {
for(auto& category : this->write_preprocess_queues) {
std::lock_guard work_lock{category.work_lock};
std::lock_guard queue_lock{category.queue_lock};
category.queue.clear();
}
this->id_generator_.reset();
}
bool PacketEncoder::encode_packet(const std::shared_ptr<protocol::ServerPacket> &original_packet, EncodeFlags flags) {
std::shared_ptr<protocol::ServerPacket> packet;
if(flags & EncodeFlags::no_copy) {
packet = original_packet;
} else {
packet = protocol::ServerPacket::from_buffer(original_packet->buffer().dup(buffer::allocate_buffer(original_packet->buffer().length())));
if(original_packet->getListener())
packet->setListener(std::move(original_packet->getListener()));
packet->memory_state.flags = original_packet->memory_state.flags;
}
auto type = EncodeProcessCategory::from_type(packet->type().type());
auto& queue = this->write_preprocess_queues[type];
if(flags & EncodeFlags::sync) {
std::string error{};
std::vector<pipes::buffer> buffers{};
this->process_count++;
{
std::unique_lock work_lock{queue.work_lock};
auto encode_result = this->encode_packet_(error, buffers, packet, work_lock);
if(encode_result != PacketEncodeResult::SUCCESS) {
if(auto callback{this->callback_encode_failed}; callback)
callback(this->callback_argument, original_packet, encode_result, error);
goto sync_cleanup_exit;
}
}
if(auto callback{this->callback_encoded_buffers}; callback)
callback(this->callback_argument, buffers);
sync_cleanup_exit:
this->process_count--; /* we're now done preparing */
return false;
} else {
std::lock_guard queue_lock{queue.queue_lock};
queue.queue.push_back(packet);
queue.has_packets = true;
}
return true;
}
bool PacketEncoder::do_encode() {
std::string error{};
std::vector<pipes::buffer> buffers{};
std::shared_ptr<protocol::ServerPacket> packet{nullptr};
bool flag_more{false};
this->process_count++; /* we're preparing a packet */
for(auto& category : this->write_preprocess_queues) {
if(!category.has_packets) {
continue;
} else if(packet) {
flag_more = true;
break;
}
std::unique_lock work_lock{category.work_lock, std::try_to_lock};
if(!work_lock) continue; /* This particular category will already be processed */
{
std::lock_guard buffer_lock{category.queue_lock};
if(category.queue.empty()) {
category.has_packets = false;
continue;
}
packet = std::move(category.queue.front());
category.queue.pop_front();
category.has_packets = !category.queue.empty();
flag_more = category.has_packets;
}
if(auto errc = this->encode_packet_(error, buffers, packet, work_lock); errc != PacketEncodeResult::SUCCESS) {
if(auto callback{this->callback_encode_failed}; callback)
callback(this->callback_argument, packet, errc, error);
if(flag_more)
break;
else
continue; /* find out if we have more */
}
if(flag_more)
break;
else
continue; /* find out if we have more */
}
/* enqueue buffers for write */
if(!buffers.empty()) {
if(auto callback{this->callback_encoded_buffers}; callback)
callback(this->callback_argument, buffers);
}
this->process_count--; /* we're now done preparing */
return flag_more;
}
PacketEncodeResult PacketEncoder::encode_packet_(std::string& error,
std::vector<pipes::buffer> &result,
const std::shared_ptr<protocol::ServerPacket> &packet,
std::unique_lock<std::mutex> &work_lock) {
assert(work_lock.owns_lock());
if(packet->type().compressable() && !packet->memory_state.fragment_entry) {
packet->enable_flag(PacketFlag::Compressed);
if(!this->compress_handler_->progressPacketOut(&*packet, error))
return PacketEncodeResult::COMPRESS_FAILED;
}
std::vector<shared_ptr<ServerPacket>> fragments;
fragments.reserve((size_t) (packet->data().length() / packet->type().max_length()) + 1);
if(packet->data().length() > packet->type().max_length()) {
if(!packet->type().fragmentable())
return PacketEncodeResult::PACKET_TOO_LARGE;
{ //Split packets
auto buffer = packet->data();
const auto max_length = packet->type().max_length();
while(buffer.length() > max_length * 2) {
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer.view(0, max_length).dup(buffer::allocate_buffer(max_length))));
buffer = buffer.range((size_t) max_length);
}
if(buffer.length() > max_length) { //Divide rest by 2
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer.view(0, buffer.length() / 2).dup(buffer::allocate_buffer(buffer.length() / 2))));
buffer = buffer.range(buffer.length() / 2);
}
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer));
for(const auto& frag : fragments) {
frag->setFragmentedEntry(true);
frag->enable_flag(PacketFlag::NewProtocol);
}
}
assert(fragments.size() >= 2);
fragments.front()->enable_flag(PacketFlag::Fragmented);
if(packet->has_flag(PacketFlag::Compressed))
fragments.front()->enable_flag(PacketFlag::Compressed);
fragments.back()->enable_flag(PacketFlag::Fragmented);
if(packet->getListener())
fragments.back()->setListener(std::move(packet->getListener())); //Move the listener to the last :)
} else {
fragments.push_back(packet);
}
result.reserve(fragments.size());
/* apply packet ids */
for(const auto& fragment : fragments) {
if(!fragment->memory_state.id_branded)
fragment->applyPacketId(this->id_generator_);
}
work_lock.unlock(); /* the rest could be unordered */
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
auto statistics = this->statistics_;
for(const auto& fragment : fragments) {
if(fragment->has_flag(PacketFlag::Unencrypted)) {
this->crypt_handler_->write_default_mac(fragment->mac().data_ptr());
} else {
if(this->protocol_encrypted) {
if(!this->crypt_handler_->generate_key_nonce(false, fragment->type().type(), fragment->packetId(), fragment->generationId(), crypt_key, crypt_nonce))
return PacketEncodeResult::ENCRYPT_KEY_GEN_FAILED;
} else {
crypt_key = CryptHandler::default_key;
crypt_nonce = CryptHandler::default_nonce;
}
auto crypt_result = this->crypt_handler_->encrypt(fragment->header().data_ptr(), fragment->header().length(),
fragment->data().data_ptr(), fragment->data().length(),
fragment->mac().data_ptr(),
crypt_key, crypt_nonce, error);
if(!crypt_result)
return PacketEncodeResult::ENCRYPT_FAILED;
}
if(statistics)
statistics->logOutgoingPacket(*fragment);
this->acknowledge_handler_->process_packet(*fragment);
result.push_back(fragment->buffer());
}
return PacketEncodeResult::SUCCESS;
}

View File

@ -0,0 +1,122 @@
#pragma once
#include <misc/spin_lock.h>
#include <mutex>
#include <deque>
#include <protocol/Packet.h>
namespace ts::connection {
class CryptHandler;
class CompressionHandler;
class AcknowledgeManager;
}
namespace ts::stats {
class ConnectionStatistics;
}
namespace ts::server::server::udp {
struct EncodeProcessCategory {
enum value {
PING_PONG = 0, //Ping/Pongs
ACK = 2,
VOICE_WHISPER = 1, //Voice/Whisper
COMMAND = 3,
INIT = 4,
MAX = INIT
};
inline static value from_type(protocol::PacketType type) {
switch(type) {
case protocol::PING:
case protocol::PONG:
return value::PING_PONG;
case protocol::VOICE:
case protocol::VOICE_WHISPER:
return value::VOICE_WHISPER;
case protocol::ACK:
case protocol::ACK_LOW:
return value::ACK;
case protocol::COMMAND:
case protocol::COMMAND_LOW:
return value::COMMAND;
default:
return value::INIT;
}
}
};
enum struct PacketEncodeResult {
SUCCESS,
COMPRESS_FAILED, /* has custom message */
PACKET_TOO_LARGE,
ENCRYPT_KEY_GEN_FAILED,
ENCRYPT_FAILED, /* has custom message */
};
class PacketEncoder {
public:
enum EncodeFlags {
none = 0x0,
no_copy = 0x1, /* do not copy the packet */
sync = 0x02 /* directly process the packet */
};
//typedef std::function<void(const std::vector<pipes::buffer>& /* buffers */)> callback_encoded_buffers_t;
//typedef std::function<void(const std::shared_ptr<protocol::ServerPacket> &/* the packet */, PacketEncodeResult /* error */, std::string& /* custom message */)> callback_encode_failed_t;
/* gets better optimized out */
typedef void(*callback_encoded_buffers_t)(void* /* cb argument */, const std::vector<pipes::buffer>& /* buffers */);
typedef void(*callback_encode_failed_t)(void* /* cb argument */, const std::shared_ptr<protocol::ServerPacket> &/* the packet */, PacketEncodeResult /* error */, const std::string& /* custom message */);
PacketEncoder(connection::CryptHandler* /* crypt handler */, connection::CompressionHandler* /* compress handler */, connection::AcknowledgeManager* /* acknowledge handler */);
~PacketEncoder();
void reset();
/* returns true if the encoder has something to encode */
bool encode_packet(const std::shared_ptr<protocol::ServerPacket> &/* the packet */, EncodeFlags /* flags */);
bool do_encode();
[[nodiscard]] inline protocol::PacketIdManager& id_generator() { return this->id_generator_; }
[[nodiscard]] inline std::shared_ptr<stats::ConnectionStatistics> get_statistics() { return this->statistics_; }
inline void set_statistics(const std::shared_ptr<stats::ConnectionStatistics>& stats) { this->statistics_ = stats; }
[[nodiscard]] inline bool is_protocol_encrypted() const { return this->protocol_encrypted; }
void set_protocol_encrypted(bool flag) { this->protocol_encrypted = flag; }
void* callback_argument{nullptr};
callback_encoded_buffers_t callback_encoded_buffers{};
callback_encode_failed_t callback_encode_failed{};
private:
bool protocol_encrypted{false};
std::shared_ptr<stats::ConnectionStatistics> statistics_{nullptr};
connection::CryptHandler* crypt_handler_{nullptr};
connection::CompressionHandler* compress_handler_{nullptr};
connection::AcknowledgeManager* acknowledge_handler_{nullptr};
struct PacketEncodeQueue {
bool has_packets{false};
std::mutex work_lock{};
spin_lock queue_lock{};
std::deque<std::shared_ptr<protocol::ServerPacket>> queue{};
};
std::array<PacketEncodeQueue, EncodeProcessCategory::MAX> write_preprocess_queues{};
/* ---------- Processing ---------- */
/* automatically locked because packets of the same kind should be lock their "work_lock" from their WritePreprocessQueue object */
protocol::PacketIdManager id_generator_{};
std::atomic<size_t> process_count{0};
PacketEncodeResult encode_packet_(std::string& /* error */, std::vector<pipes::buffer> &result/* buffers which need to be transferred */, const std::shared_ptr<protocol::ServerPacket> &packet/* the packet */, std::unique_lock<std::mutex> &work_lock /* work lock */);
};
}

View File

@ -0,0 +1,69 @@
//
// Created by WolverinDEV on 11/03/2020.
//
#include "PingHandler.h"
using namespace ts::server::server::udp;
void PingHandler::reset() {
this->last_ping_id = 0;
this->current_ping_ = std::chrono::milliseconds{0};
this->last_recovery_command_send = std::chrono::system_clock::time_point{};
this->last_command_acknowledge_ = std::chrono::system_clock::time_point{};
this->last_response_ = std::chrono::system_clock::time_point{};
this->last_request_ = std::chrono::system_clock::time_point{};
}
void PingHandler::received_pong(uint16_t ping_id) {
if(this->last_ping_id != ping_id) return;
auto now = std::chrono::system_clock::now();
this->current_ping_ = std::chrono::floor<std::chrono::milliseconds>(this->last_request_ - now);
this->last_response_ = now;
this->last_command_acknowledge_ = now; /* That's here for purpose!*/
}
void PingHandler::received_command_acknowledged() {
this->last_command_acknowledge_ = std::chrono::system_clock::now();
}
void PingHandler::tick(const std::chrono::system_clock::time_point& now) {
if(this->last_request_ + PingHandler::ping_request_interval < now)
this->send_ping_request(); /* may update last_response_ */
if(this->last_response_ + PingHandler::ping_timeout < now) {
if(this->last_recovery_command_send + PingHandler::recovery_request_interval < now)
this->send_recovery_request();
if(this->last_command_acknowledge_ + PingHandler::recovery_timeout < now) {
if(auto callback{this->callback_time_outed}; callback)
callback(this->callback_argument);
}
}
}
void PingHandler::send_ping_request() {
auto now = std::chrono::system_clock::now();
if(this->last_response_.time_since_epoch().count() == 0)
this->last_response_ = now;
this->last_request_ = now;
if(auto callback{this->callback_send_ping}; callback)
callback(this->callback_argument, this->last_ping_id);
}
void PingHandler::send_recovery_request() {
auto now = std::chrono::system_clock::now();
if(this->last_command_acknowledge_.time_since_epoch().count() == 0)
this->last_command_acknowledge_ = now;
this->last_recovery_command_send = now;
if(auto callback{this->callback_send_recovery_command}; callback)
callback(this->callback_argument);
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <cstdint>
#include <chrono>
namespace ts::server::server::udp {
class PingHandler {
public:
typedef void(*callback_time_outed_t)(void* /* cb data */);
typedef void(*callback_send_ping_t)(void* /* cb data */, uint16_t& /* ping id */);
typedef void(*callback_send_recovery_command_t)(void* /* cb data */);
void reset();
void tick(const std::chrono::system_clock::time_point&);
void received_pong(uint16_t /* ping id */);
void received_command_acknowledged();
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
void* callback_argument{nullptr};
callback_send_ping_t callback_send_ping{nullptr};
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
callback_time_outed_t callback_time_outed{nullptr};
private:
constexpr static std::chrono::milliseconds ping_request_interval{2500};
constexpr static std::chrono::milliseconds ping_timeout{10 * 1000};
constexpr static std::chrono::milliseconds recovery_request_interval{1000};
constexpr static std::chrono::milliseconds recovery_timeout{10 * 1000};
std::chrono::milliseconds current_ping_{0};
uint16_t last_ping_id{0};
std::chrono::system_clock::time_point last_response_{};
std::chrono::system_clock::time_point last_request_{};
std::chrono::system_clock::time_point last_command_acknowledge_{};
std::chrono::system_clock::time_point last_recovery_command_send{};
void send_ping_request();
void send_recovery_request();
};
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <ThreadPool/Mutex.h>
#include <tommath.h>
#include <memory>
#include <deque>
namespace ts {
namespace server {
class ConnectedClient;
}
namespace protocol {
struct Puzzle {
mp_int x;
mp_int n;
int level;
mp_int result;
uint8_t data_x[64];
uint8_t data_n[64];
uint8_t data_result[64];
};
class PuzzleManager {
public:
PuzzleManager();
~PuzzleManager();
bool precomputePuzzles(size_t limit);
size_t precomputedPuzzleCount();
std::shared_ptr<Puzzle> nextPuzzle();
private:
void generatePuzzle();
threads::Mutex indexLock;
size_t cacheIndex = 0;
std::deque<std::shared_ptr<Puzzle>> cached;
};
}
}

View File

@ -1,22 +1,19 @@
#include <algorithm>
#include <memory>
#include <tomcrypt.h>
#include <arpa/inet.h>
#include <ThreadPool/Timer.h>
#include <misc/endianness.h>
#include <misc/memtracker.h>
#include <log/LogUtils.h>
#include "VoiceClient.h"
#include "src/VirtualServer.h"
#include "../../server/VoiceServer.h"
using namespace std;
using namespace std::chrono;
using namespace ts::server;
using namespace ts::protocol;
VoiceClient::VoiceClient(const std::shared_ptr<VoiceServer>& server, const sockaddr_storage* address) : SpeakingClient(server->server->sql, server->server), voice_server(server) {
VoiceClient::VoiceClient(const std::shared_ptr<VirtualServer>& server, const sockaddr_storage* address) : SpeakingClient(server->sql, server) {
assert(address);
memtrack::allocated<VoiceClient>(this);
memcpy(&this->remote_address, address, sizeof(sockaddr_storage));
@ -24,27 +21,21 @@ VoiceClient::VoiceClient(const std::shared_ptr<VoiceServer>& server, const socka
debugMessage(this->server->getServerId(), " Creating VoiceClient instance at {}", (void*) this);
}
void VoiceClient::initialize() {
this->event_handle_packet = make_shared<event::ProxiedEventEntry<VoiceClient>>(dynamic_pointer_cast<VoiceClient>(this->ref()), &VoiceClient::execute_handle_packet);
VoiceClient::~VoiceClient() {
debugMessage(this->getServerId(), " Deleting VoiceClient instance at {}", (void*) this);
this->state = ClientState::DISCONNECTED;
memtrack::freed<VoiceClient>(this);
}
void VoiceClient::initialize(const std::shared_ptr<connection::VoiceClientConnection> &connection) {
assert(connection);
this->connection_ = connection;
this->properties()[property::CLIENT_TYPE] = ClientType::CLIENT_TEAMSPEAK;
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_TEAMSPEAK;
this->state = ConnectionState::INIT_HIGH;
this->connection = new connection::VoiceClientConnection(this);
}
VoiceClient::~VoiceClient() {
debugMessage(this->getServerId(), " Deleting VoiceClient instance at {}", (void*) this);
this->state = ConnectionState::DISCONNECTED;
delete this->connection;
this->connection = nullptr;
if(this->flushing_thread)
logCritical(this->getServerId(), "Deleting a VoiceClient which should still be hold within the flush thread!");
memtrack::freed<VoiceClient>(this);
this->state = ClientState::INITIALIZING;
}
void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, bool direct, std::unique_ptr<threads::Future<bool>> listener) {
@ -61,59 +52,26 @@ void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, bool direc
packet->enable_flag(protocol::PacketFlag::NewProtocol);
}
packet->setListener(std::move(listener));
this->connection->sendPacket(packet, false, direct);
if(auto connection{this->connection_}; connection)
connection->send_packet(packet, false, direct);
#ifdef PKT_LOG_CMD
logTrace(this->getServerId(), "{}[Command][Server -> Client] Sending command {}. Command low: {}. Full command: {}", CLIENT_STR_LOG_PREFIX, cmd.substr(0, cmd.find(' ')), low, cmd);
#endif
}
void VoiceClient::sendAcknowledge(uint16_t packetId, bool low) {
char buffer[2];
le2be16(packetId, buffer);
auto packet = make_shared<protocol::ServerPacket>(low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, pipes::buffer_view{buffer, 2});
packet->enable_flag(PacketFlag::Unencrypted);
if(!low) packet->enable_flag(protocol::PacketFlag::NewProtocol);
this->connection->sendPacket(packet);
#ifdef PKT_LOG_ACK
logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId);
#endif
}
void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
SpeakingClient::tick(time);
{
ALARM_TIMER(A1, "VoiceClient::tick", milliseconds(3));
if(this->state == ConnectionState::CONNECTED) {
if(this->lastPingRequest > this->lastPingResponse) { //Client is behind :)
if(this->lastPingRequest - this->lastPingResponse > chrono::seconds(20)) {
debugMessage(this->getServerId(), "{} Got a ping timeout. (Last successful ping: {}ms ago. Last request {}ms. Last response {}ms). Trying to recover via command acknowledge.",
CLIENT_STR_LOG_PREFIX,
duration_cast<milliseconds>(this->lastPingRequest - this->lastPingResponse).count(),
duration_cast<milliseconds>(time - this->lastPingRequest).count(),
duration_cast<milliseconds>(time - this->lastPingResponse).count());
bool force;
this->request_connection_info(nullptr, force);
this->lastPingResponse = system_clock::now();
return;
}
}
if(time - this->lastPingRequest >= chrono::milliseconds(1000)) {
//TODO calculate the ping smooth
if(this->lastPingResponse < this->lastPingRequest){
if(time - this->lastPingRequest >= chrono::milliseconds(1500)) { //Max
this->sendPingRequest();
}
} else
this->sendPingRequest();
}
} else if(this->state == ConnectionState::INIT_LOW || this->state == ConnectionState::INIT_HIGH) {
//TODO!
if(this->state == ClientState::INITIALIZING) {
if(this->last_packet_handshake.time_since_epoch().count() != 0) {
if(time - this->last_packet_handshake > seconds(5)) {
debugMessage(this->getServerId(), "{} Got handshake timeout. {}. State: {} Time: {}", CLIENT_STR_LOG_PREFIX,
debugMessage(this->getServerId(), "{} Got initialize timeout. {}. Time: {}", CLIENT_STR_LOG_PREFIX,
this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()),
this->state == ConnectionState::INIT_HIGH ? "INIT_HIGH" : "INIT_LOW",
duration_cast<seconds>(time - this->last_packet_handshake).count()
);
this->close_connection(system_clock::now() + seconds(1));
@ -133,18 +91,18 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas
* Its only for the clients own flavour and everything which the client receives after will be ignored :)
*/
ConnectionState old_state{};
ClientState old_state{};
{
std::lock_guard state_lock{this->state_lock};
if(this->state == ConnectionState::DISCONNECTING || this->state == ConnectionState::DISCONNECTED)
if(this->state == ClientState::DISCONNECTED)
return false; //Already disconnecting/disconnected
old_state = this->state;
this->state = ConnectionState::DISCONNECTING;
this->state = ClientState::DISCONNECTED;
}
if(old_state == ConnectionState::CONNECTED) {
/* Client has been successflly initialized; Send normal disconnect. */
if(old_state == ClientState::CONNECTED) {
/* Client has been successfully initialized; Send normal disconnect. */
Command cmd("notifyclientleftview");
cmd["reasonmsg"] = reason;
@ -193,120 +151,67 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas
}
bool VoiceClient::close_connection(const system_clock::time_point &timeout) {
auto self_lock = dynamic_pointer_cast<VoiceClient>(_this.lock());
assert(self_lock); //Should never happen!
bool flush = timeout.time_since_epoch().count() > 0;
{
std::lock_guard state_lock{this->state_lock};
if(this->state == ConnectionState::DISCONNECTED) return false;
else if(this->state == ConnectionState::DISCONNECTING) {
/* here is nothing to pay attention for */
} else if(this->state == ConnectionState::DISCONNECTING_FLUSHING) {
if(!flush) {
this->state = ConnectionState::DISCONNECTED;
return true; /* the flush thread will execute the final disconnect */
} else {
//TODO: May update the flush timeout if its less then the other one?
return true;
}
}
this->state = flush ? ConnectionState::DISCONNECTING_FLUSHING : ConnectionState::DISCONNECTED;
this->state = ClientState::DISCONNECTED;
}
debugMessage(this->getServerId(), "{} Closing voice client connection. (Flush: {})", CLIENT_STR_LOG_PREFIX, flush);
//TODO: Move this out into a thread pool?
this->flushing_thread = std::make_shared<threads::Thread>(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [this, self_lock, timeout, flush]{
{
/* Await that all commands have been processed. It does not make sense to unregister the client while command handling. */
std::lock_guard cmd_lock{this->command_lock};
}
if(flush) {
debugMessage(this->getServerId(), "{} Awaiting write prepare, write and acknowledge queue flushed", CLIENT_STR_LOG_PREFIX);
while(this->state == DISCONNECTING_FLUSHING) {
if(system_clock::now() > timeout){
auto write_queue_flushed = this->connection->wait_empty_write_and_prepare_queue(timeout);
auto acknowledge_received = connection->acknowledge_handler.awaiting_acknowledge() == 0;
if(write_queue_flushed && acknowledge_received)
break;
debugMessage(this->getServerId(), "{} Failed to flush pending messages. Acknowledges pending: {} Buffers pending: {}", CLIENT_STR_LOG_PREFIX, acknowledge_received, write_queue_flushed);
break;
}
if(!this->connection->wait_empty_write_and_prepare_queue(timeout))
continue;
if(connection->acknowledge_handler.awaiting_acknowledge() > 0) {
usleep(5000);
continue;
}
debugMessage(this->getServerId(), "{} Write and acknowledge queue are flushed", CLIENT_STR_LOG_PREFIX);
break;
}
}
if(this->state > DISCONNECTING) /* it could happen that the client "reconnects" while flushing this shit */
this->finalDisconnect();
});
flushing_thread->name("Flush thread VC").execute();
auto connection = std::exchange(this->connection_, nullptr);
if(connection) connection->close_connection(timeout);
return true;
}
void VoiceClient::finalDisconnect() {
void VoiceClient::finalize() {
/* there could not happen any IO while we're doing finalize()! */
auto ownLock = dynamic_pointer_cast<VoiceClient>(_this.lock());
assert(ownLock);
lock_guard disconnect_lock_final(this->finalDisconnectLock);
#if 0
if(this->final_disconnected) {
logError(this->getServerId(), "Tried to final disconnect {}/{} twice", this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()), this->getDisplayName());
return;
}
this->final_disconnected = true;
this->state = ConnectionState::DISCONNECTED;
#endif
this->state = ClientState::DISCONNECTED;
threads::MutexLock command_lock(this->command_lock); //We should not progress any commands while disconnecting
//Unload manager cache
this->processLeave();
{
if(this->flushing_thread) this->flushing_thread->detach(); //The thread itself should be already done or executing this method
this->flushing_thread.reset();
}
if(this->voice_server) this->voice_server->unregisterConnection(ownLock);
}
void VoiceClient::execute_handle_packet(const std::chrono::system_clock::time_point &time) {
this->connection->execute_handle_command_packets(time);
}
void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
auto packet = make_shared<ServerPacket>(PacketTypeInfo::Voice, voice_buffer.length());
{
PacketFlag::PacketFlags packet_flags = PacketFlag::None;
packet_flags |= flags.encrypted ? 0 : PacketFlag::Unencrypted;
packet_flags |= flags.head ? PacketFlag::Compressed : 0;
packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0;
packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0;
packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted;
packet_flags |= flags.head ? PacketFlag::Compressed : 0U;
packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0U;
packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0U;
packet->set_flags(packet_flags);
}
memcpy(packet->data().data_ptr<void>(), voice_buffer.data_ptr<void>(), voice_buffer.length());
this->connection->sendPacket(packet, false, false);
if(auto connection{this->connection_}; connection)
connection->send_packet(packet, false, false);
}
void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &voice_buffer, const SpeakingClient::VoicePacketFlags &flags) {
auto packet = make_shared<ServerPacket>(PacketTypeInfo::VoiceWhisper, voice_buffer.length());
{
PacketFlag::PacketFlags packet_flags = PacketFlag::None;
packet_flags |= flags.encrypted ? 0 : PacketFlag::Unencrypted;
packet_flags |= flags.head ? PacketFlag::Compressed : 0;
packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0;
packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0;
packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted;
packet_flags |= flags.head ? PacketFlag::Compressed : 0U;
packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0U;
packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0U;
packet->set_flags(packet_flags);
}
memcpy(packet->data().data_ptr<void>(), voice_buffer.data_ptr<void>(), voice_buffer.length());
this->connection->sendPacket(packet, false, false);
if(auto connection{this->connection_}; connection)
connection->send_packet(packet, false, false);
}
std::chrono::milliseconds VoiceClient::calculatePing() {
auto connection = this->connection_;
return connection ? connection->ping_handler_.current_ping() : std::chrono::milliseconds{0};
}

View File

@ -7,13 +7,12 @@
#include <netinet/in.h>
#include <deque>
#include <cstdint>
#include <src/server/VoiceServer.h>
#include <EventLoop.h>
#include "../SpeakingClient.h"
#include "../ConnectedClient.h"
#include "protocol/CryptHandler.h"
#include "VoiceClientConnection.h"
#include "PrecomputedPuzzles.h"
#include "src/server/udp-server/PrecomputedPuzzles.h"
#include "../../lincense/TeamSpeakLicense.h"
//#define LOG_INCOMPING_PACKET_FRAGMENTS
@ -42,12 +41,10 @@ namespace ts {
class VoiceClient : public SpeakingClient {
friend class VirtualServer;
friend class VoiceServer;
friend class POWHandler;
friend class ts::connection::VoiceClientConnection;
friend class ConnectedClient;
friend class io::IOServerHandler;
public:
VoiceClient(const std::shared_ptr<VoiceServer>& server,const sockaddr_storage*);
VoiceClient(const std::shared_ptr<VirtualServer>& server,const sockaddr_storage*);
~VoiceClient();
bool close_connection(const std::chrono::system_clock::time_point &timeout) override;
@ -59,53 +56,30 @@ namespace ts {
/* Note: Order is only guaranteed if progressDirectly is on! */
virtual void sendCommand0(const std::string_view& /* data */, bool low = false, bool progressDirectly = false, std::unique_ptr<threads::Future<bool>> listener = nullptr);
virtual void sendAcknowledge(uint16_t packetId, bool low = false);
connection::VoiceClientConnection* getConnection(){ return connection; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return voice_server; }
std::chrono::milliseconds calculatePing(){ return ping; }
/* the connection might be null! */
[[nodiscard]] inline auto connection() { return this->connection_; }
[[nodiscard]] std::chrono::milliseconds calculatePing();
private:
connection::VoiceClientConnection* connection;
std::shared_ptr<connection::VoiceClientConnection> connection_{nullptr};
protected:
std::shared_ptr<VoiceServer> voice_server;
void initialize(const std::shared_ptr<connection::VoiceClientConnection>& /* connection */);
void finalize();
void initialize();
virtual void tick(const std::chrono::system_clock::time_point &time) override;
void handlePacketCommand(const pipes::buffer_view&);
void handlePacketAck(const protocol::ClientPacketParser&);
void handlePacketVoice(const protocol::ClientPacketParser&);
void handlePacketPing(const protocol::ClientPacketParser&);
void handlePacketInit(const protocol::ClientPacketParser&);
//Handshake helpers
public:
void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
void send_voice_whisper_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override;
protected:
void handlePacketCommand(const pipes::buffer_view&);
virtual command_result handleCommand(Command &command) override;
//Some helper method
void sendPingRequest();
//Ping/pong
uint16_t lastPingId = 0;
std::chrono::milliseconds ping = std::chrono::milliseconds(0);
std::chrono::system_clock::time_point lastPingResponse;
std::chrono::system_clock::time_point lastPingRequest;
std::chrono::system_clock::time_point last_packet_handshake;
private:
int socket = 0;
io::pktinfo_storage address_info;
void finalDisconnect();
bool final_disconnected = false;
//General TS3 manager commands
command_result handleCommandClientInitIv(Command&);
@ -113,13 +87,9 @@ namespace ts {
command_result handleCommandClientInit(Command&) override;
command_result handleCommandClientDisconnect(Command&);
//Locked by finalDisconnect, disconnect and close connection
std::shared_ptr<threads::Thread> flushing_thread;
struct {
bool client_init = false;
bool new_protocol = false;
bool protocol_encrypted = false;
uint32_t client_time = 0;
std::string alpha;
@ -128,8 +98,11 @@ namespace ts {
std::shared_ptr<ecc_key> remote_key;
} crypto;
std::shared_ptr<event::ProxiedEventEntry<VoiceClient>> event_handle_packet;
void execute_handle_packet(const std::chrono::system_clock::time_point& /* scheduled */);
enum struct CryptoHandshakeState {
INITEV,
CLIENT_EK,
DONE
} crypto_handshake_state{CryptoHandshakeState::INITEV};
};
}
}

View File

@ -1,12 +1,11 @@
#include <log/LogUtils.h>
#include <misc/endianness.h>
#include <misc/base64.h>
#include <ThreadPool/Timer.h>
#include <openssl/sha.h>
#include <misc/digest.h>
#include <src/client/SpeakingClient.h>
#include "../../InstanceHandler.h"
#include "../../geo/GeoLocation.h"
#include "VoiceClient.h"
using namespace std;
@ -18,10 +17,10 @@ using namespace ts;
command_result VoiceClient::handleCommand(ts::Command &command) {
threads::MutexLock l2(this->command_lock);
if(this->state == ConnectionState::DISCONNECTED) return command_result{error::client_not_logged_in};
if(this->state == ClientState::DISCONNECTED) return command_result{error::client_not_logged_in};
if(!this->voice_server) return command_result{error::server_unbound};
if(this->state == ConnectionState::INIT_HIGH && this->handshake.state == HandshakeState::SUCCEEDED) {
if(this->state == ClientState::INITIALIZING && this->crypto_handshake_state == CryptoHandshakeState::DONE) {
if(command.command() == "clientinit")
return this->handleCommandClientInit(command);
} else if(command.command() == "clientdisconnect")

View File

@ -1,26 +1,13 @@
#include <misc/endianness.h>
#include <algorithm>
#include <log/LogUtils.h>
#include "../../server/VoiceServer.h"
#include <misc/memtracker.h>
#include <protocol/Packet.h>
#include <ThreadPool/Timer.h>
#include "VoiceClientConnection.h"
#include "src/client/ConnectedClient.h"
#include "VoiceClient.h"
//#define LOG_AUTO_ACK_AUTORESPONSE
//#define FUZZING_TESTING_INCOMMING
//#define FUZZING_TESTING_OUTGOING
//#define FIZZING_TESTING_DISABLE_HANDSHAKE
#define FUZZING_TESTING_DROP 8
#define FUZZING_TESTING_DROP_MAX 10
//#define CONNECTION_NO_STATISTICS
#define QLZ_COMPRESSION_LEVEL 1
#include "qlz/QuickLZ.h"
#include "./VoiceClientConnection.h"
#include "./VoiceClient.h"
#include "src/server/udp-server/UDPServer.h"
#include "src/InstanceHandler.h"
using namespace std;
using namespace std::chrono;
@ -29,33 +16,84 @@ using namespace ts::connection;
using namespace ts::protocol;
using namespace ts::server;
VoiceClientConnection::VoiceClientConnection(VoiceClient* client) : client(client) {
VoiceClientConnection::VoiceClientConnection(server::server::udp::Server* server, const std::shared_ptr<server::VoiceClient>& client, int socket) :
socket{socket},
udp_server{server},
packet_encoder_{&this->crypt_handler, &this->compress_handler, &this->acknowledge_handler},
packet_decoder_{&this->crypt_handler} {
memtrack::allocated<VoiceClientConnection>(this);
this->packet_encoder_.callback_argument = this;
this->packet_encoder_.callback_encoded_buffers = [](auto _this, const auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encoded_buffers(a1);
};
this->packet_encoder_.callback_encode_failed = [](auto _this, const auto& a1, auto a2, const auto& a3) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encode_error(a1, a2, a3);
};
this->packet_decoder_.callback_argument = this;
this->packet_decoder_.callback_decoded_packet = [](auto _this, const auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decoded_packet(a1);
};
this->packet_decoder_.callback_decode_failed = [](auto _this, auto a1, const auto& a2) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decode_error(a1, a2);
};
this->packet_decoder_.callback_send_acknowledge = [](auto _this, auto a1, auto a2) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_acknowledge(a1, a2);
};
this->ping_handler_.callback_argument = this;
this->ping_handler_.callback_send_ping = [](auto _this, auto& a1) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping(a1);
};
this->ping_handler_.callback_send_recovery_command = [](auto _this) {
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping_recovery();
};
this->ping_handler_.callback_time_outed = [](auto _this) {
reinterpret_cast<VoiceClientConnection*>(_this)->handle_ping_timeout();
};
this->server_id = client->getServerId();
this->client_handle_ = client;
this->crypt_handler.reset();
debugMessage(client->getServer()->getServerId(), "Allocated new voice client connection at {}", (void*) this);
debugMessage(this->server_id, "Allocated new voice client connection at {}", (void*) this);
}
void VoiceClientConnection::initialize(const std::shared_ptr<VoiceClientConnection> &self) {
assert(&*self == this);
this->weak_this = self;
this->event_handle_packet = make_shared<event::ProxiedEventEntry<VoiceClientConnection>>(self, &VoiceClientConnection::execute_handle_command_packets);
}
VoiceClientConnection::~VoiceClientConnection() {
debugMessage(this->server_id, "Deleted voice client connection at {}", (void*) this);
/* locking here should be useless, but just to ensure! */
{
lock_guard write_queue_lock(this->write_queue_lock);
lock_guard wqlock(this->write_queue_lock);
this->write_queue.clear();
}
for(auto& category : this->write_preprocess_queues) {
lock_guard work_lock{category.work_lock};
lock_guard queue_lock{category.queue_lock};
category.queue.clear();
}
this->client = nullptr;
this->client_handle_ = nullptr;
memtrack::freed<VoiceClientConnection>(this);
}
void VoiceClientConnection::triggerWrite() {
if(this->client->voice_server)
this->client->voice_server->triggerWrite(dynamic_pointer_cast<VoiceClient>(this->client->_this.lock()));
void VoiceClientConnection::register_for_write() {
auto self = this->weak_this.lock();
assert(self);
this->udp_server->schedule_client_write(self);
}
void VoiceClientConnection::register_for_command_handling() {
auto vmanager = serverInstance->getVoiceServerManager();
if(!vmanager)
return;
auto evloop = vmanager->get_executor_loop();
if(!evloop)
return;
evloop->schedule(this->event_handle_packet);
}
#ifdef CLIENT_LOG_PREFIX
@ -66,520 +104,233 @@ void VoiceClientConnection::triggerWrite() {
//Message handle methods
void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& buffer) {
#ifdef FUZZING_TESTING_INCOMMING
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
if (this->client->state == ConnectionState::CONNECTED) {
#endif
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
auto command_pending = this->packet_decoder_.decode_incoming_data(buffer);
if(command_pending) this->register_for_command_handling();
}
bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer) {
return this->packet_decoder_.verify_encryption(buffer);
}
void VoiceClientConnection::handle_decoded_packet(const ts::protocol::ClientPacketParser &packet) {
auto packet_type = packet.type();
if(packet_type == PacketType::VOICE) {
auto client = this->client_handle_;
if(!client) [[unlikely]] {
logWarning(this->server_id, "Received voice data for client, but we've no client associated with the connection.");
return;
}
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
}
#endif
#endif
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid()) {
logTrace(this->client->getServerId(), "{} Received invalid packet. Dropping.", CLIENT_STR_LOG_PREFIX_(this->client));
return;
}
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()));
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) {
/* handle the order stuff */
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
unique_lock queue_lock(fragment_buffer.buffer_lock);
auto result = fragment_buffer.accept_index(packet_parser.packet_id());
if(result != 0) { /* packet index is ahead buffer index */
debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {} ({}|{}|{})",
CLIENT_STR_LOG_PREFIX_(this->client),
result == -1 ? "underflow" : "overflow",
fragment_buffer.capacity(),
fragment_buffer.current_index(),
packet_parser.packet_id()
);
if(result == -1) { /* underflow */
/* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */
if(this->client->crypto.protocol_encrypted)
this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
}
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
} else if(packet_type == PacketType::VOICE_WHISPER) {
auto client = this->client_handle_;
if(!client) [[unlikely]] {
logWarning(this->server_id, "Received voice whisper data for client, but we've no client associated with the connection.");
return;
}
}
//NOTICE I found out that the Compressed flag is set if the packet contains an audio header
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
} else if(packet_type == PacketType::ACK || packet_type == PacketType::ACK_LOW) {
string error{};
if(!this->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
debugMessage(this->server_id, "{} Failed to handle acknowledge: {}", this->client_log_prefix(), error);
this->ping_handler_.received_command_acknowledged();
} else if(packet_type == PacketType::PING) {
/* just send a pong response */
char buffer[2];
le2be16(packet.packet_id(), buffer);
auto pkt = make_shared<ServerPacket>(PacketTypeInfo::Pong, pipes::buffer_view{buffer, 2});
pkt->enable_flag(PacketFlag::Unencrypted);
this->send_packet(pkt);
} else if(packet_type == PacketType::PONG) {
if(packet.payload_length() < 2) return;
if(this->client->state == ConnectionState::INIT_LOW && packet_parser.type() != protocol::INIT1)
return;
/* decrypt the packet if needed */
if(packet_parser.is_encrypted()) {
std::string error;
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
bool use_default_key{!this->client->crypto.protocol_encrypted}, decrypt_result;
decrypt_packet:
if(use_default_key) {
crypt_key = CryptHandler::default_key;
crypt_nonce = CryptHandler::default_nonce;
} else {
if(!this->crypt_handler.generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce)) {
logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
return;
}
}
decrypt_result = this->crypt_handler.decrypt(
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
data,
crypt_key, crypt_nonce,
error
);
if(!decrypt_result) {
if(!this->client->crypto.client_init) {
if(use_default_key) {
logTrace(this->client->getServerId(), "{} Failed to decrypt packet with default key ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error);
return;
} else {
logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Trying with default key.", CLIENT_STR_LOG_PREFIX_(this->client), error);
use_default_key = true;
goto decrypt_packet;
}
} else {
logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error);
return;
}
}
packet_parser.set_decrypted();
} else if(is_command && this->client->state != ConnectionState::INIT_HIGH) {
logTrace(this->client->getServerId(), "{} Voice client {}/{} tried to send a unencrypted command packet. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), client->getDisplayName(), this->client->getLoggingPeerIp());
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
if(is_command) {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
CommandFragment fragment_entry{
packet_parser.packet_id(),
packet_parser.estimated_generation(),
packet_parser.flags(),
(uint32_t) packet_parser.payload_length(),
packet_parser.payload().own_buffer()
};
{
unique_lock queue_lock(fragment_buffer.buffer_lock);
if(!fragment_buffer.insert_index(packet_parser.packet_id(), std::move(fragment_entry))) {
logTrace(this->client->getServerId(), "{} Failed to insert command packet into command packet buffer.", CLIENT_STR_LOG_PREFIX_(this->client));
return;
}
}
this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
auto voice_server = this->client->voice_server;
if(voice_server)
voice_server->schedule_command_handling(this->client);
} else {
if(packet_parser.type() == protocol::VOICE || packet_parser.type() == protocol::VOICE_WHISPER)
this->client->handlePacketVoice(packet_parser);
else if(packet_parser.type() == protocol::ACK || packet_parser.type() == protocol::ACK_LOW)
this->client->handlePacketAck(packet_parser);
else if(packet_parser.type() == protocol::PING || packet_parser.type() == protocol::PONG)
this->client->handlePacketPing(packet_parser);
else {
logError(this->client->getServerId(), "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
}
this->ping_handler_.received_pong(be2le16((char*) packet.payload().data_ptr()));
} else if(packet_type == PacketType::COMMAND || packet_type == PacketType::COMMAND_LOW) {
logCritical(this->server_id, "{} Received command packet within handle_decoded_packet callback.", this->client_log_prefix());
} else if(packet_type == PacketType::INIT1) {
logCritical(this->server_id, "{} Received init packet within handle_decoded_packet callback.", this->client_log_prefix());
}
}
bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer /* incl. mac etc */) {
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
void VoiceClientConnection::handle_decode_error(ts::server::server::udp::PacketDecodeResult error, const std::string &message) {
using PacketDecodeResult = ts::server::server::udp::PacketDecodeResult;
switch (error) {
case PacketDecodeResult::DECRYPT_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet. Failed to decrypt packet ({}).", this->client_log_prefix(), message);
break;
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
return this->crypt_handler.verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
case PacketDecodeResult::DECRYPT_KEY_GEN_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet. Failed to generate crypto key for packet.", this->client_log_prefix());
break;
case PacketDecodeResult::BUFFER_OVERFLOW:
logWarning(this->server_id, "{} Dropping incoming packet because queue has a buffer overflow ({}).", this->client_log_prefix(), message);
break;
case PacketDecodeResult::COMMAND_INSTERT_FAILED:
logWarning(this->server_id, "{} Dropping incoming packet because we failed to register the command packet.", this->client_log_prefix());
break;
#if 0
case PacketDecodeResult::DUPLICATED_PACKET:
logWarning(this->server_id, "{} Dropping incoming packet because it has already be processed.", this->client_log_prefix());
break;
case PacketDecodeResult::INVALID_PACKET:
logWarning(this->server_id, "{} Dropping incoming packet because its invalid.", this->client_log_prefix());
break;
#else
case PacketDecodeResult::INVALID_PACKET:
case PacketDecodeResult::DUPLICATED_PACKET:
#endif
case PacketDecodeResult::SUCCESS:
break;
}
}
void VoiceClientConnection::send_packet_acknowledge(uint16_t packet_id, bool command_low) {
char buffer[2];
le2be16(packet_id, buffer);
auto packet = make_shared<protocol::ServerPacket>(command_low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, pipes::buffer_view{buffer, 2});
packet->enable_flag(PacketFlag::Unencrypted);
if(!command_low) packet->enable_flag(protocol::PacketFlag::NewProtocol);
this->send_packet(packet);
#ifdef PKT_LOG_ACK
logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId);
#endif
}
void VoiceClientConnection::send_packet_ping(uint16_t& ping_id) {
auto packet = make_shared<ServerPacket>(PacketTypeInfo::Ping, pipes::buffer_view{});
packet->enable_flag(PacketFlag::Unencrypted);
this->send_packet(packet, false, true); /* prepare directly so the packet gets a packet id */
ping_id = packet->packetId();
}
void VoiceClientConnection::send_packet_ping_recovery() {
const char* command = "notifyserverpingrecovery";
auto packet = make_shared<protocol::ServerPacket>(protocol::PacketTypeInfo::Command,
pipes::buffer_view{(void*) command, strlen(command)}
);
this->send_packet(packet);
}
void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
if(this->client->state >= ConnectionState::DISCONNECTING || !this->client->getServer())
if((int) this->connection_state_ >= (int) ClientConnectionState::DISCONNECTING)
return;
//TODO: Remove the buffer_execute_lock and use the one within the this->client->handlePacketCommand method
unique_lock<std::recursive_timed_mutex> buffer_execute_lock;
pipes::buffer payload{};
uint16_t packet_id{};
auto reexecute_handle = this->next_reassembled_command(buffer_execute_lock, payload, packet_id);
if(!payload.empty()){
auto startTime = system_clock::now();
try {
this->client->handlePacketCommand(payload);
} catch (std::exception& ex) {
logCritical(this->client->getServerId(), "{} Exception reached root tree! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what());
}
auto end = system_clock::now();
if(end - startTime > milliseconds(10)) {
logError(this->client->getServerId(),
"{} Handling of command packet needs more than 10ms ({}ms)",
CLIENT_STR_LOG_PREFIX_(this->client),
duration_cast<milliseconds>(end - startTime).count()
);
}
}
if(buffer_execute_lock.owns_lock())
buffer_execute_lock.unlock();
auto voice_server = this->client->voice_server;
if(voice_server && reexecute_handle)
this->client->voice_server->schedule_command_handling(this->client);
}
/* buffer_execute_lock: lock for in order execution */
bool VoiceClientConnection::next_reassembled_command(unique_lock<std::recursive_timed_mutex>& buffer_execute_lock, pipes::buffer& result, uint16_t& packet_id) {
command_fragment_buffer_t* buffer{nullptr};
unique_lock<std::recursive_timed_mutex> buffer_lock; /* general buffer lock */
bool have_more{false};
{
//FIXME: Currently command low packets cant be handeled if there is a command packet stuck in reassamble
/* 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;
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;
buffer_lock = move(ring_lock);
buffer = &buf;
} else {
have_more = true;
break;
}
}
}
}
if(!buffer)
return false; /* we've no packets */
uint8_t packet_flags{0};
pipes::buffer payload{};
/* lets find out if we've to reassemble the packet */
auto& first_buffer = buffer->slot_value(0);
packet_id = first_buffer.packet_id;
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
uint16_t sequence_length{1};
size_t total_payload_length{first_buffer.payload_length};
do {
if(sequence_length >= buffer->capacity()) {
logError(this->client->getServerId(), "{} Command fragment buffer is full, and there is not fragmented packet end. Dropping full buffer which will probably cause a connection loss.", CLIENT_STR_LOG_PREFIX_(this->client));
buffer->clear();
return false; /* we've nothing to handle */
}
if(!buffer->slot_set(sequence_length))
return false; /* we need more packets */
auto& packet = buffer->slot_value(sequence_length++);
total_payload_length += packet.payload_length;
if(packet.packet_flags & PacketFlag::Fragmented) {
/* yep we find the end */
break;
}
} while(true);
/* ok we have all fragments lets reassemble */
/*
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
* We're checking it here because we dont want to make a huge allocation
*/
assert(total_payload_length < 512 * 1024 * 1024);
pipes::buffer packet_buffer{total_payload_length};
char* packet_buffer_ptr = &packet_buffer[0];
size_t packet_count{0};
packet_flags = buffer->slot_value(0).packet_flags;
while(packet_count < sequence_length) {
auto fragment = buffer->pop_front();
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
packet_buffer_ptr += fragment.payload_length;
packet_count++;
}
#ifndef _NDEBUG
if((packet_buffer_ptr - 1) != &packet_buffer[packet_buffer.length() - 1]) {
logCritical(this->client->getServer()->getServerId(),
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
(void*) packet_buffer_ptr,
(void*) &packet_buffer[packet_buffer.length() - 1]
);
}
#endif
payload = packet_buffer;
} else {
auto packet = buffer->pop_front();
packet_flags = packet.packet_flags;
payload = packet.payload;
}
have_more |= buffer->front_set(); /* set the more flag if we have more to process */
buffer_lock.unlock();
if(packet_flags & PacketFlag::Compressed) {
std::string error{};
auto decompressed_size = compression::qlz_decompressed_size(payload.data_ptr(), payload.length());
auto buffer = buffer::allocate_buffer(decompressed_size);
if(!compression::qlz_decompress_payload(payload.data_ptr(), buffer.data_ptr(), &decompressed_size)) {
logTrace(this->client->getServerId(), "{} Failed to decompress received command. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
return false;
}
payload = buffer.range(0, decompressed_size);
}
result = std::move(payload);
return have_more;
}
void VoiceClientConnection::sendPacket(const shared_ptr<protocol::ServerPacket>& original_packet, bool copy, bool prepare_directly) {
if(this->client->state == ConnectionState::DISCONNECTED)
auto client = this->client_handle_;
if(!client) [[unlikely]]
return;
std::lock_guard clock{client->command_lock};
shared_ptr<protocol::ServerPacket> packet;
if(copy) {
packet = protocol::ServerPacket::from_buffer(original_packet->buffer().dup(buffer::allocate_buffer(original_packet->buffer().length())));
if(original_packet->getListener())
packet->setListener(std::move(original_packet->getListener()));
packet->memory_state.flags = original_packet->memory_state.flags;
} else {
packet = original_packet;
}
using CommandReassembleResult = ts::server::server::udp::CommandReassembleResult;
auto type = WritePreprocessCategory::from_type(packet->type().type());
auto& queue = this->write_preprocess_queues[type];
if(prepare_directly) {
vector<pipes::buffer> buffers;
this->prepare_process_count++;
{
unique_lock work_lock{queue.work_lock};
if(!this->prepare_packet_for_write(buffers, packet, work_lock)) {
logError(this->client->getServerId(), "{} Dropping packet!", CLIENT_STR_LOG_PREFIX_(this->client));
this->prepare_process_count--;
return;
}
}
/* enqueue buffers for write */
{
lock_guard write_queue_lock(this->write_queue_lock);
this->write_queue.insert(this->write_queue.end(), buffers.begin(), buffers.end());
}
this->prepare_process_count--; /* we're now done preparing */
} else {
lock_guard queue_lock{queue.queue_lock};
queue.queue.push_back(packet);
queue.has_work = true;
}
this->triggerWrite();
}
bool VoiceClientConnection::prepare_packet_for_write(vector<pipes::buffer> &result, const shared_ptr<ServerPacket> &packet, std::unique_lock<std::mutex>& work_lock) {
assert(work_lock.owns_lock());
string error = "success";
if(packet->type().compressable() && !packet->memory_state.fragment_entry) {
packet->enable_flag(PacketFlag::Compressed);
if(!this->compress_handler.progressPacketOut(packet.get(), error)) {
logError(this->getClient()->getServerId(), "{} Could not compress outgoing packet.\nThis could cause fatal failed for the client.\nError: {}", error);
return false;
}
}
std::vector<shared_ptr<ServerPacket>> fragments;
fragments.reserve((size_t) (packet->data().length() / packet->type().max_length()) + 1);
if(packet->data().length() > packet->type().max_length()) {
if(!packet->type().fragmentable()) {
logError(this->client->getServerId(), "{} We've tried to send a too long, not fragmentable, packet. Dropping packet of type {} with length {}", CLIENT_STR_LOG_PREFIX_(this->client), packet->type().name(), packet->data().length());
return false;
}
{ //Split packets
auto buffer = packet->data();
const auto max_length = packet->type().max_length();
while(buffer.length() > max_length * 2) {
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer.view(0, max_length).dup(buffer::allocate_buffer(max_length))));
buffer = buffer.range((size_t) max_length);
}
if(buffer.length() > max_length) { //Divide rest by 2
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer.view(0, buffer.length() / 2).dup(buffer::allocate_buffer(buffer.length() / 2))));
buffer = buffer.range(buffer.length() / 2);
}
fragments.push_back(make_shared<ServerPacket>(packet->type(), buffer));
for(const auto& frag : fragments) {
frag->setFragmentedEntry(true);
frag->enable_flag(PacketFlag::NewProtocol);
}
}
assert(fragments.size() >= 2);
fragments.front()->enable_flag(PacketFlag::Fragmented);
if(packet->has_flag(PacketFlag::Compressed))
fragments.front()->enable_flag(PacketFlag::Compressed);
fragments.back()->enable_flag(PacketFlag::Fragmented);
if(packet->getListener())
fragments.back()->setListener(std::move(packet->getListener())); //Move the listener to the last :)
} else {
fragments.push_back(packet);
}
result.reserve(fragments.size());
/* apply packet ids */
for(const auto& fragment : fragments) {
if(!fragment->memory_state.id_branded)
fragment->applyPacketId(this->packet_id_manager);
}
work_lock.unlock(); /* the rest could be unordered */
CryptHandler::key_t crypt_key{};
CryptHandler::nonce_t crypt_nonce{};
auto statistics = this->client ? this->client->connectionStatistics : nullptr;
for(const auto& fragment : fragments) {
if(fragment->has_flag(PacketFlag::Unencrypted)) {
this->crypt_handler.write_default_mac(fragment->mac().data_ptr());
} else {
if(!this->client->crypto.protocol_encrypted) {
crypt_key = CryptHandler::default_key;
crypt_nonce = CryptHandler::default_nonce;
} else {
if(!this->crypt_handler.generate_key_nonce(false, fragment->type().type(), fragment->packetId(), fragment->generationId(), crypt_key, crypt_nonce)) {
logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client));
return false;
}
}
auto crypt_result = this->crypt_handler.encrypt(fragment->header().data_ptr(), fragment->header().length(),
fragment->data().data_ptr(), fragment->data().length(),
fragment->mac().data_ptr(),
crypt_key, crypt_nonce, error);
if(!crypt_result){
logError(this->client->getServerId(), "{} Failed to encrypt packet. Error: {}", CLIENT_STR_LOG_PREFIX_(this->client), error);
return false;
}
}
#ifndef CONNECTION_NO_STATISTICS
if(statistics)
statistics->logOutgoingPacket(*fragment);
#endif
this->acknowledge_handler.process_packet(*fragment);
result.push_back(fragment->buffer());
}
return true;
}
bool VoiceClientConnection::preprocess_write_packets() {
std::shared_ptr<ServerPacket> packet{nullptr};
vector<pipes::buffer> buffers{};
bool flag_more{false};
prepare_process_count++; /* we're not preparing a packet */
for(auto& category : this->write_preprocess_queues) {
if(!category.has_work) continue;
else if(packet) {
flag_more = true;
pipes::buffer payload{};
bool command_low{false};
auto command_status = this->packet_decoder_.reassemble_command(payload, command_low);
switch (command_status) {
case CommandReassembleResult::SUCCESS:
case CommandReassembleResult::MORE_COMMANDS_PENDING:
break;
}
unique_lock work_lock{category.work_lock, try_to_lock};
if(!work_lock) continue; /* This particular category will already be processed */
case CommandReassembleResult::NO_COMMANDS_PENDING:
return;
{
lock_guard buffer_lock{category.queue_lock};
if(category.queue.empty()) {
category.has_work = false;
continue;
}
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
case CommandReassembleResult::COMMAND_TOO_LARGE:
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
//TODO: Shutdown connection?
logError(this->server_id, "{} Failed to reassemble next command ({}). This will cause the connection to fail.", this->client_log_prefix(), (int) command_status);
return;
}
packet = std::move(category.queue.front());
category.queue.pop_front();
category.has_work = !category.queue.empty();
flag_more = category.has_work;
}
auto startTime = system_clock::now();
try {
client->handlePacketCommand(payload);
} catch (std::exception& ex) {
logCritical(this->server_id, "{} An exception has been thrown within command handling, which reached to root handler. This should not happen! (Message: {})", this->client_log_prefix(), ex.what());
}
if(!this->prepare_packet_for_write(buffers, packet, work_lock)) {
logError(this->client->getServerId(), "{} Dropping packet!", CLIENT_STR_LOG_PREFIX_(this->client));
if(flag_more)
break;
else
continue; /* find out if we have more */
}
if(flag_more)
break;
auto end = system_clock::now();
if(end - startTime > milliseconds(10)) {
auto index = payload.find(" ");
std::string command{};
if(index == std::string::npos)
command = payload.string();
else
continue; /* find out if we have more */
command = payload.view(0, index).string();
logWarning(this->server_id, "{} Command handling of command \"{}\" required more than 10ms ({}ms)",this->client_log_prefix(),
command,
std::chrono::duration_cast<std::chrono::milliseconds>(end - startTime).count()
);
}
/* enqueue buffers for write */
if(!buffers.empty()) {
lock_guard write_queue_lock(this->write_queue_lock);
this->write_queue.insert(this->write_queue.end(), buffers.begin(), buffers.end());
}
this->prepare_process_count--; /* we're now done preparing */
return flag_more;
if(command_status == CommandReassembleResult::MORE_COMMANDS_PENDING)
this->register_for_command_handling();
}
int VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
if(this->client->state == DISCONNECTED)
return 2;
lock_guard write_queue_lock(this->write_queue_lock);
void VoiceClientConnection::send_packet(const shared_ptr<protocol::ServerPacket>& original_packet, bool copy, bool prepare_directly) {
if(this->connection_state_ == ClientConnectionState::DISCONNECTED)
return;
using EncodeFlags = server::server::udp::PacketEncoder::EncodeFlags;
int flags{EncodeFlags::none};
if(!copy)
flags |= (unsigned) EncodeFlags::no_copy;
if(prepare_directly)
flags |= (unsigned) EncodeFlags::sync;
if(this->packet_encoder_.encode_packet(original_packet, (EncodeFlags) flags))
this->register_for_write();
}
void VoiceClientConnection::handle_encode_error(const shared_ptr<protocol::ServerPacket> &packet,
ts::server::server::udp::PacketEncodeResult result, const std::string &message) {
using PacketEncodeResult = ts::server::server::udp::PacketEncodeResult;
switch (result) {
case PacketEncodeResult::PACKET_TOO_LARGE:
logWarning(this->server_id, "{} Dropping packet of type {}. Packet is too large ({}bytes).", this->client_log_prefix(), packet->type().name(), packet->length());
break;
case PacketEncodeResult::COMPRESS_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to compress packet ({}).", this->client_log_prefix(), packet->type().name(), message);
break;
case PacketEncodeResult::ENCRYPT_KEY_GEN_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to generate crypto key for packet.", this->client_log_prefix(), packet->type().name());
break;
case PacketEncodeResult::ENCRYPT_FAILED:
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to encrypt packet ({}).", this->client_log_prefix(), packet->type().name(), message);
break;
case PacketEncodeResult::SUCCESS:
break;
}
}
void VoiceClientConnection::handle_encoded_buffers(const std::vector<pipes::buffer> &buffers) {
{
std::lock_guard lock{this->write_queue_lock};
this->write_queue.insert(this->write_queue.begin(), buffers.begin(), buffers.end());
}
this->register_for_write();
}
bool VoiceClientConnection::encode_packets() {
return this->packet_encoder_.do_encode();
}
WriteBufferStatus VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
lock_guard wqlock(this->write_queue_lock);
size_t size = this->write_queue.size();
if(size == 0)
return 2;
return WriteBufferStatus::EMPTY;
target = std::move(this->write_queue.front());
this->write_queue.pop_front();
@ -596,85 +347,41 @@ int VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
}
#endif
#endif
return size > 1;
}
bool VoiceClientConnection::wait_empty_write_and_prepare_queue(chrono::time_point<chrono::system_clock> until) {
while(true) {
for(auto& queue : this->write_preprocess_queues) {
{
lock_guard lock{queue.queue_lock};
if(!queue.queue.empty())
goto _wait;
}
{
unique_lock lock{queue.work_lock, try_to_lock};
if(!lock.owns_lock())
goto _wait;
}
}
{
lock_guard buffer_lock{this->write_queue_lock};
if(!this->write_queue.empty())
goto _wait;
if(this->prepare_process_count != 0)
goto _wait;
}
break;
_wait:
if(until.time_since_epoch().count() != 0 && system_clock::now() > until)
return false;
threads::self::sleep_for(milliseconds(5));
}
return true;
return size > 1 ? WriteBufferStatus::BUFFERS_LEFT : WriteBufferStatus::EMPTY;
}
void VoiceClientConnection::reset() {
for(auto& queue : this->write_preprocess_queues) {
{
lock_guard lock{queue.queue_lock};
queue.queue.clear();
}
}
this->packet_encoder_.reset();
this->packet_decoder_.reset();
this->acknowledge_handler.reset();
this->crypt_handler.reset();
this->packet_id_manager.reset();
{
lock_guard buffer_lock(this->packet_buffer_lock);
for(auto& buffer : this->_command_fragment_buffers)
buffer.reset();
}
}
void VoiceClientConnection::force_insert_command(const pipes::buffer_view &buffer) {
CommandFragment fragment_entry{
0,
0,
PacketFlag::Unencrypted,
(uint32_t) buffer.length(),
buffer.own_buffer()
};
{
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
unique_lock queue_lock(fragment_buffer.buffer_lock);
fragment_buffer.push_front(std::move(fragment_entry));
}
auto voice_server = this->client->voice_server;
if(voice_server)
voice_server->schedule_command_handling(this->client);
this->packet_decoder_.force_insert_command(buffer);
this->register_for_command_handling();
}
void VoiceClientConnection::register_initiv_packet() {
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
unique_lock buffer_lock(fragment_buffer.buffer_lock);
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
void VoiceClientConnection::close_connection(const std::chrono::system_clock::time_point &timeout) {
//TODO!
if(timeout.time_since_epoch().count() > 0) {
} else {
this->connection_state_ = ClientConnectionState::DISCONNECTED;
/* Unregister connection from server */
}
}
void VoiceClientConnection::tick() {
auto now = std::chrono::system_clock::now();
this->ping_handler_.tick(now);
if(this->connection_state_ == ClientConnectionState::DISCONNECTING) {
//TODO!
if(now > this->disconnect_timeout_) {
//TODO!
}
}
}

View File

@ -15,6 +15,9 @@
#include "VoiceClient.h"
#include "protocol/AcknowledgeManager.h"
#include <protocol/generation.h>
#include "./PacketEncoder.h"
#include "./PacketDecoder.h"
#include "./PingHandler.h"
//#define LOG_ACK_SYSTEM
#ifdef LOG_ACK_SYSTEM
@ -27,151 +30,114 @@
namespace ts {
namespace server {
class VoiceClient;
class VoiceServer;
class POWHandler;
namespace server::udp {
class Server;
}
}
namespace connection {
enum struct WriteBufferStatus {
EMPTY,
BUFFERS_LEFT,
NO_CHANGES,
UNSET
};
enum struct ClientConnectionState {
INITIALIZING, /* crypto setup */
CONNECTED, /* basic connection has been established */
DISCONNECTING, /* connection is already disconnecting */
DISCONNECTED /* connection has been (maybe successfully) closed */
};
class VoiceClientConnection {
friend class AcknowledgeManager;
friend class server::VoiceServer;
friend class server::VoiceClient;
friend class server::POWHandler;
public:
struct CommandFragment {
uint16_t packet_id{0};
uint16_t packet_generation{0};
uint8_t packet_flags{0};
uint32_t payload_length : 24;
pipes::buffer payload{};
CommandFragment() { this->payload_length = 0; }
CommandFragment(uint16_t packetId, uint16_t packetGeneration, uint8_t packetFlags, uint32_t payloadLength, pipes::buffer payload) : packet_id{packetId}, packet_generation{packetGeneration}, packet_flags{packetFlags},
payload_length{payloadLength}, payload{std::move(payload)} {}
CommandFragment& operator=(const CommandFragment&) = default;
CommandFragment(const CommandFragment& other) = default;
CommandFragment(CommandFragment&&) = default;
};
static_assert(sizeof(CommandFragment) == 8 + sizeof(pipes::buffer));
typedef protocol::PacketRingBuffer<CommandFragment, 32, CommandFragment> command_fragment_buffer_t;
typedef std::array<command_fragment_buffer_t, 2> command_packet_reassembler;
explicit VoiceClientConnection(server::VoiceClient*);
explicit VoiceClientConnection(server::server::udp::Server*, const std::shared_ptr<server::VoiceClient>&, int /* socket */);
virtual ~VoiceClientConnection();
void sendPacket(const std::shared_ptr<protocol::ServerPacket>& original_packet, bool copy = false, bool prepare_directly = false);
void initialize(const std::shared_ptr<VoiceClientConnection>& /* self */);
CryptHandler* getCryptHandler(){ return &crypt_handler; }
[[nodiscard]] inline CryptHandler* getCryptHandler(){ return &crypt_handler; }
//[[nodiscard]] inline server::VoiceClient* getClient(){ return client; }
server::VoiceClient* getClient(){ return client; }
[[nodiscard]] inline server::server::udp::PacketEncoder& packet_encoder() { return this->packet_encoder_; }
[[nodiscard]] inline server::server::udp::PacketDecoder& packet_decoder() { return this->packet_decoder_; }
[[nodiscard]] inline ClientConnectionState connection_state() const { return this->connection_state_; }
void send_packet(const std::shared_ptr<protocol::ServerPacket>& original_packet, bool copy = false, bool prepare_directly = false);
#ifdef VC_USE_READ_QUEUE
bool handleNextDatagram();
#endif
/*
* Split packets waiting in write_process_queue and moves the final buffers to writeQueue.
* @returns true when there are more packets to prepare
*/
bool preprocess_write_packets();
bool encode_packets();
/* return 2 => Nothing | 1 => More and buffer is set | 0 => Buffer is set, nothing more */
int pop_write_buffer(pipes::buffer& /* buffer */);
[[nodiscard]] WriteBufferStatus pop_write_buffer(pipes::buffer& /* buffer */);
bool wait_empty_write_and_prepare_queue(std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
/* a flush timout less than now will cause the client to close the connection instantly */
void close_connection(const std::chrono::system_clock::time_point& /* flush timeout */);
protocol::PacketIdManager& getPacketIdManager() { return this->packet_id_manager; }
void reset();
void tick(); /* called via the UDP server tick */
void force_insert_command(const pipes::buffer_view& /* payload */);
void register_initiv_packet();
//buffer::SortedBufferQueue<protocol::ClientPacket>** getReadQueue() { return this->readTypedQueue; }
void send_packet_acknowledge(uint16_t /* packet id */, bool /* is command low */);
void send_packet_ping(uint16_t& /* ping id */);
void send_packet_ping_recovery();
protected:
void handle_incoming_datagram(const pipes::buffer_view &buffer);
bool verify_encryption(const pipes::buffer_view& /* full packet */);
void triggerWrite();
void register_for_write();
void register_for_command_handling();
private:
server::VoiceClient* client = nullptr;
std::weak_ptr<VoiceClientConnection> weak_this{};
//Decryption / encryption stuff
CryptHandler crypt_handler; /* access to CryptHandler is thread save */
CompressionHandler compress_handler;
AcknowledgeManager acknowledge_handler;
server::server::udp::Server* udp_server;
int socket{0};
io::pktinfo_storage address_info{};
//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 */);
VirtualServerId server_id{0};
/* may change at any given time. */
std::shared_ptr<server::VoiceClient> client_handle_{nullptr};
ClientConnectionState connection_state_{ClientConnectionState::INITIALIZING};
std::chrono::system_clock::time_point disconnect_timeout_{};
CryptHandler crypt_handler{};
CompressionHandler compress_handler{};
AcknowledgeManager acknowledge_handler{};
/* ---------- Write declarations ---------- */
spin_lock write_queue_lock; /* queue access isn't for long in general */
std::deque<pipes::buffer> write_queue;
struct WritePreprocessCategory {
enum value {
PING_PONG = 0, //Ping/Pongs
ACK = 2,
VOICE_WHISPER = 1, //Voice/Whisper
COMMAND = 3,
INIT = 4,
server::server::udp::PacketEncoder packet_encoder_;
server::server::udp::PacketDecoder packet_decoder_;
server::server::udp::PingHandler ping_handler_{};
MAX = INIT
};
inline static value from_type(protocol::PacketType type) {
switch(type) {
case protocol::PING:
case protocol::PONG:
return value::PING_PONG;
std::shared_ptr<event::ProxiedEventEntry<VoiceClientConnection>> event_handle_packet;
case protocol::VOICE:
case protocol::VOICE_WHISPER:
return value::VOICE_WHISPER;
//Handle stuff
[[nodiscard]] std::string client_log_prefix();
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
case protocol::ACK:
case protocol::ACK_LOW:
return value::ACK;
/* will be called on the IO thread or if sync has been set directly in any thread */
void handle_encode_error(const std::shared_ptr<protocol::ServerPacket> &/* the packet */, ts::server::server::udp::PacketEncodeResult /* error */, const std::string& /* custom message */);
void handle_encoded_buffers(const std::vector<pipes::buffer>& /* buffers */);
case protocol::COMMAND:
case protocol::COMMAND_LOW:
return value::COMMAND;
default:
return value::INIT;
}
}
};
struct WritePreprocessQueue {
int _zero1{0};
bool has_work{false};
std::mutex work_lock{};
spin_lock queue_lock{};
std::deque<std::shared_ptr<protocol::ServerPacket>> queue{};
int _zero{0};
};
std::array<WritePreprocessQueue, WritePreprocessCategory::MAX> write_preprocess_queues{};
/* ---------- Processing ---------- */
/* automatically locked because packets of the same kind should be lock their "work_lock" from their WritePreprocessQueue object */
protocol::PacketIdManager packet_id_manager;
/* this function is thread save :) */
std::atomic<uint8_t> prepare_process_count{0}; /* current thread count preparing a packet */
bool prepare_packet_for_write(std::vector<pipes::buffer> &/* buffers which need to be transferred */, const std::shared_ptr<protocol::ServerPacket> &/* the packet */, std::unique_lock<std::mutex>& /* work lock */);
std::array<protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
std::recursive_mutex packet_buffer_lock;
command_packet_reassembler _command_fragment_buffers;
static inline uint8_t command_fragment_buffer_index(uint8_t packet_index) {
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
}
/* will be called on the IO thread */
void handle_decoded_packet(const protocol::ClientPacketParser&);
void handle_decode_error(ts::server::server::udp::PacketDecodeResult /* error */, const std::string& /* custom message */);
void handle_ping_timeout();
};
}
}

View File

@ -23,10 +23,13 @@ inline void generate_random(uint8_t *destination, size_t length) {
}
ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
auto connection = this->connection_;
if(!connection) return ts::command_result{error::vs_critical};
this->last_packet_handshake = system_clock::now();
std::unique_lock state_lock{this->state_lock};
if(this->state == ConnectionState::CONNECTED) { /* we've a reconnect */
if(this->state == ClientState::CONNECTED) { /* we've a reconnect */
if(system_clock::now() - this->lastPingResponse < seconds(5)) {
logMessage(this->getServerId(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
CLIENT_STR_LOG_PREFIX,
@ -53,24 +56,24 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
this->server->client_move(this->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock);
}
this->finalDisconnect();
this->finalize();
state_lock.lock();
} else if(this->state >= ConnectionState::DISCONNECTING) {
state_lock.unlock();
std::shared_lock disconnect_finish{this->finalDisconnectLock}; /* await until the last disconnect has been processed */
state_lock.lock();
this->state = ConnectionState::INIT_HIGH;
} else if(this->state == ConnectionState::INIT_HIGH) {
logTrace(this->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX);
return command_result{error::ok};
} else if(this->state == ClientState::DISCONNECTED) {
this->state = ClientState::INITIALIZING;
this->crypto_handshake_state = CryptoHandshakeState::INITEV;
connection->reset();
} else {
this->state = ConnectionState::INIT_HIGH;
assert(this->state == ClientState::INITIALIZING);
if(this->crypto_handshake_state != CryptoHandshakeState::INITEV) {
logTrace(this->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handler should handle this issue for us.", CLIENT_STR_LOG_PREFIX);
return command_result{error::ok};
}
}
state_lock.unlock();
this->connection->reset();
this->connection->register_initiv_packet();
this->crypto.protocol_encrypted = false;
connection->reset();
connection->packet_decoder().register_initiv_packet();
connection->packet_decoder().set_protocol_encrypted(false);
bool use_teaspeak = command.hasParm("teaspeak");
if(use_teaspeak ? !config::server::clients::teaspeak : !config::server::clients::teamspeak)
@ -145,6 +148,7 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
initivexpand2["ot"] = 1;
this->sendCommand(initivexpand2);
this->crypto_handshake_state = CryptoHandshakeState::CLIENT_EK;
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
} else {
debugMessage(this->getServerId(), "{} Got non client 3.1 protocol with build timestamp {}", CLIENT_STR_LOG_PREFIX, this->crypto.client_time);
@ -167,30 +171,41 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */
}
this->sendCommand0(initivexpand.build(), false, true); //If we setup the encryption now
this->crypto_handshake_state = CryptoHandshakeState::DONE;
}
{
string error;
if(!this->connection->getCryptHandler()->setupSharedSecret(this->crypto.alpha, this->crypto.beta, this->crypto.remote_key.get(), this->server->serverKey(), error)){
if(!connection->getCryptHandler()->setupSharedSecret(this->crypto.alpha, this->crypto.beta, this->crypto.remote_key.get(), this->server->serverKey(), error)){
logError(this->server->getServerId(), "Could not setup shared secret! (" + error + ")");
return ts::command_result{error::vs_critical};
}
this->crypto.protocol_encrypted = true;
auto& decoder = connection->packet_decoder();
decoder.set_protocol_encrypted(true);
}
}
return ts::command_result{error::ok};
}
ts::command_result VoiceClient::handleCommandClientEk(Command& cmd) {
auto connection = this->connection_;
if(!connection) return ts::command_result{error::vs_critical};
if(this->crypto_handshake_state != CryptoHandshakeState::CLIENT_EK) return ts::command_result{error::client_hacked};
this->last_packet_handshake = system_clock::now();
debugMessage(this->getServerId(), "{} Got client ek!", CLIENT_STR_LOG_PREFIX);
auto client_key = base64::decode(cmd["ek"]);
auto private_key = this->crypto.chain_data->chain->generatePrivateKey(this->crypto.chain_data->root_key, this->crypto.chain_data->root_index);
this->connection->getCryptHandler()->setupSharedSecretNew(this->crypto.alpha, this->crypto.beta, (char*) private_key.data(), client_key.data());
this->connection->acknowledge_handler.reset();
this->crypto.protocol_encrypted = true;
this->sendAcknowledge(1); //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop)
connection->getCryptHandler()->setupSharedSecretNew(this->crypto.alpha, this->crypto.beta, (char*) private_key.data(), client_key.data());
connection->acknowledge_handler.reset();
this->crypto_handshake_state = CryptoHandshakeState::DONE;
auto& decoder = connection->packet_decoder();
decoder.set_protocol_encrypted(true);
connection->send_packet_acknowledge(1, false); //Send the encrypted acknowledge (most the times the second packet; If not we're going into the resend loop)
return ts::command_result{error::ok};
}

View File

@ -1,9 +1,6 @@
#include <tommath.h>
#include <misc/endianness.h>
#include <algorithm>
#include <log/LogUtils.h>
#include "../web/WebClient.h"
#include "VoiceClient.h"
#include "./VoiceClient.h"
using namespace std;
using namespace std::chrono;
@ -11,11 +8,12 @@ using namespace ts::server;
using namespace ts::protocol;
//#define PKT_LOG_PING
/* should never happen! */
void VoiceClient::handlePacketInit(const ts::protocol::ClientPacketParser &) {}
//TODO Packet handlers -> move back to voice client?
void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string) {
{
std::lock_guard slock{this->state_lock};
if(this->state == ClientState::DISCONNECTED) return;
}
std::unique_ptr<Command> command;
command_result result{};
try {
@ -40,48 +38,4 @@ void VoiceClient::handlePacketCommand(const pipes::buffer_view& command_string)
handle_error:
this->notifyError(result);
result.release_details();
}
void VoiceClient::handlePacketPing(const protocol::ClientPacketParser& packet) {
if (packet.type() == protocol::PONG) {
if(packet.payload_length() < 2) return;
uint16_t id = be2le16((char*) packet.payload().data_ptr());
if (this->lastPingId == id) {
#ifdef PKT_LOG_PING
logMessage(this->getServerId(), "{}[Ping] Got a valid pong for ping {}. Required time: {}", CLIENT_STR_LOG_PREFIX, id, duration_cast<microseconds>(system_clock::now() - this->lastPingRequest).count() / 1000.f);
#endif
this->lastPingResponse = system_clock::now();
this->ping = std::chrono::duration_cast<std::chrono::milliseconds>(this->lastPingResponse - this->lastPingRequest);
}
#ifdef PKT_LOG_PING
else {
logMessage(this->getServerId(), "{}[Ping] Got invalid pong. (Responded pong id {} but expected {})", CLIENT_STR_LOG_PREFIX, packet->packetId(), this->lastPingId);
}
#endif
return;
}
#ifdef PKT_LOG_PING
logMessage(this->getServerId(), "{}[Ping] Sending pong for client requested ping {}", CLIENT_STR_LOG_PREFIX, packet->packetId());
#endif
char buffer[2];
le2be16(packet.packet_id(), buffer);
auto pkt = make_shared<ServerPacket>(PacketTypeInfo::Pong, pipes::buffer_view{buffer, 2});
pkt->enable_flag(PacketFlag::Unencrypted);
this->connection->sendPacket(pkt);
}
void VoiceClient::handlePacketVoice(const protocol::ClientPacketParser& packet) {
if (packet.type() == protocol::VOICE) {
SpeakingClient::handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
} else if(packet.type() == protocol::VOICE_WHISPER) {
SpeakingClient::handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0);
}
}
void VoiceClient::handlePacketAck(const protocol::ClientPacketParser& packet) {
string error{};
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
debugMessage(this->getServerId(), "{} Failed to handle acknowledge: {}", CLIENT_STR_LOG_PREFIX, error);
}

View File

@ -1,24 +0,0 @@
#include <Definitions.h>
#include <log/LogUtils.h>
#include "../../InstanceHandler.h"
#include "VoiceClient.h"
using namespace std;
using namespace ts::server;
using namespace ts::protocol;
extern InstanceHandler* serverInstance;
void VoiceClient::sendPingRequest() {
this->lastPingRequest = std::chrono::system_clock::now();
auto packet = make_shared<ServerPacket>(PacketTypeInfo::Ping, pipes::buffer_view{});
packet->enable_flag(PacketFlag::Unencrypted);
this->connection->sendPacket(packet, false, true); /* prepare directly so the packet gets a packet id */
this->lastPingId = packet->packetId();
#ifdef PKT_LOG_PING
logMessage(this->getServerId(), "{}[Ping] Sending a ping request with it {}", CLIENT_STR_LOG_PREFIX, this->lastPingId);
#endif
}

View File

@ -125,7 +125,7 @@ inline bool is_ssl_handshake_header(const pipes::buffer_view& buffer) {
void WebClient::processNextMessage(const std::chrono::system_clock::time_point& /* scheduled */) {
lock_guard execute_lock(this->execute_lock);
if(this->state != ConnectionState::INIT_HIGH && this->state != ConnectionState::INIT_LOW && this->state != ConnectionState::CONNECTED)
if(this->state != ClientState::INIT_HIGH && this->state != ClientState::INITIALIZING && this->state != ClientState::CONNECTED)
return;
unique_lock buffer_lock(this->queue_lock);

View File

@ -26,7 +26,7 @@ WebClient::WebClient(WebControlServer* server, int fd) : SpeakingClient(server->
memtrack::allocated<WebClient>(this);
assert(server->getTS());
this->state = ConnectionState::INIT_LOW;
this->state = ClientState::INITIALIZING;
this->file_descriptor = fd;
}
@ -178,9 +178,9 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
assert(self_lock);
unique_lock state_lock(this->state_lock);
if(this->state == ConnectionState::DISCONNECTED) return false;
if(this->state == ConnectionState::DISCONNECTING && flushing) return true;
this->state = flushing ? ConnectionState::DISCONNECTING : ConnectionState::DISCONNECTED;
if(this->state == ClientState::DISCONNECTED) return false;
if(this->state == ClientState::DISCONNECTING && flushing) return true;
this->state = flushing ? ClientState::DISCONNECTING : ClientState::DISCONNECTED;
unique_lock close_lock(this->close_lock);
state_lock.unlock();
@ -203,7 +203,7 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
while(true) {
{
lock_guard lock(self_lock->state_lock);
if(self_lock->state != ConnectionState::DISCONNECTING) return; /* somebody else had this problem now */
if(self_lock->state != ClientState::DISCONNECTING) return; /* somebody else had this problem now */
}
flag_flushed = true;
@ -226,8 +226,8 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
{
lock_guard lock(self_lock->state_lock);
if(self_lock->state != ConnectionState::DISCONNECTING) return; /* somebody else had this problem now */
self_lock->state = ConnectionState::DISCONNECTED;
if(self_lock->state != ClientState::DISCONNECTING) return; /* somebody else had this problem now */
self_lock->state = ClientState::DISCONNECTED;
}
/* we can lock here again because we've already ensured that we're still disconnecting and updated the status to disconnected.
* So no thread will wait for this thread while close_lock had been locked
@ -246,7 +246,7 @@ bool WebClient::close_connection(const std::chrono::system_clock::time_point& ti
}
command_result WebClient::handleCommand(Command &command) {
if(this->connectionState() == ConnectionState::INIT_HIGH && this->handshake.state == HandshakeState::SUCCEEDED){
if(this->connectionState() == ClientState::INIT_HIGH && this->handshake.state == HandshakeState::SUCCEEDED){
if(command.command() == "clientinit") {
auto result = this->handleCommandClientInit(command);
if(result.error_code())
@ -289,7 +289,7 @@ void WebClient::tick(const std::chrono::system_clock::time_point& point) {
}
void WebClient::onWSConnected() {
this->state = ConnectionState::INIT_HIGH;
this->state = ClientState::INIT_HIGH;
this->handshake.state = HandshakeState::BEGIN;
debugMessage(this->getServerId(), "{} WebSocket handshake completed!", CLIENT_STR_LOG_PREFIX);
//TODO here!
@ -629,7 +629,7 @@ bool WebClient::disconnect(const std::string &reason) {
unique_lock server_channel_lock(this->server->channel_tree_lock);
{
unique_lock own_lock(this->channel_lock);
this->notifyClientLeftViewKicked(this->ref(), nullptr, reason, this->server->serverRoot, false);
this->notifyClientLeftViewKicked(this->ref(), this->currentChannel ? this->currentChannel->channelId() : 0, reason, this->server->serverRoot, false, nullptr);
}
this->server->client_move(this->ref(), nullptr, this->server->serverRoot, reason, ViewReasonId::VREASON_SERVER_KICK, false, server_channel_lock);
this->server->unregisterClient(_this.lock(), "disconnected", server_channel_lock);

View File

@ -0,0 +1,16 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include "Group.h"
#include "./GroupManager.h"
using namespace ts::server::groups;
Group::Group(VirtualServerId sid, ts::GroupId id, ts::server::groups::GroupType type, std::string name,
std::shared_ptr<permission::v2::PermissionRegister> permissions) : virtual_server_id_{sid}, group_id_{id}, type_{type}, name_{std::move(name)}, permissions_{std::move(permissions)} { }
void Group::set_permissions(const std::shared_ptr<permission::v2::PermissionRegister> &permissions) {
assert(permissions);
this->permissions_ = permissions;
}

85
server/src/groups/Group.h Normal file
View File

@ -0,0 +1,85 @@
#pragma once
#include <memory>
#include <PermissionRegister.h>
namespace ts::server::groups {
enum GroupType {
GROUP_TYPE_QUERY,
GROUP_TYPE_TEMPLATE,
GROUP_TYPE_NORMAL,
GROUP_TYPE_UNKNOWN = 0xFF
};
enum GroupNameMode {
GROUP_NAME_MODE_HIDDEN,
GROUP_NAME_MODE_BEFORE,
GROUP_NAME_MODE_BEHIND
};
typedef uint32_t GroupSortId;
typedef uint32_t GroupIconId;
class GroupManager;
class Group {
friend class GroupManager;
public:
Group(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr<permission::v2::PermissionRegister> /* permissions */);
~Group() = default;
/* information getters */
[[nodiscard]] inline VirtualServerId virtual_server_id() const { return this->virtual_server_id_; }
[[nodiscard]] inline GroupId group_id() const { return this->group_id_; }
[[nodiscard]] inline GroupType group_type() const { return this->type_; }
[[nodiscard]] inline const std::string& display_name() const { return this->name_; }
/* we're not returning a cr here because the permissions might get reloaded */
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionRegister> permissions() { return this->permissions_; }
[[nodiscard]] inline bool save_assignments() const {
assert(this->permissions_);
auto value = this->permissions_->permission_value_flagged(permission::b_group_is_permanent);
return value.has_value ? permission::v2::permission_granted(1, value) : true;
}
[[nodiscard]] inline GroupNameMode name_mode() const {
assert(this->permissions_);
auto value = this->permissions_->permission_value_flagged(permission::i_group_show_name_in_tree);
return value.has_value ? (GroupNameMode) value.value : GroupNameMode::GROUP_NAME_MODE_HIDDEN;
}
[[nodiscard]] inline GroupSortId sort_id() const {
assert(this->permissions_);
auto value = this->permissions_->permission_value_flagged(permission::i_group_sort_id);
return value.has_value ? value.value : 0;
}
[[nodiscard]] inline GroupIconId icon_id() const {
assert(this->permissions_);
auto value = this->permissions_->permission_value_flagged(permission::i_icon_id);
return value.has_value ? value.value : 0;
}
private:
const VirtualServerId virtual_server_id_;
const GroupId group_id_;
const GroupType type_;
std::string name_;
std::shared_ptr<permission::v2::PermissionRegister> permissions_;
void set_permissions(const std::shared_ptr<permission::v2::PermissionRegister>& /* permissions */);
};
class ServerGroup : public Group {
public:
ServerGroup(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr<permission::v2::PermissionRegister> /* permissions */);
};
class ChannelGroup : public Group {
public:
ChannelGroup(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr<permission::v2::PermissionRegister> /* permissions */);
};
}
DEFINE_TRANSFORMS(ts::server::groups::GroupType, uint8_t);
DEFINE_TRANSFORMS(ts::server::groups::GroupNameMode, uint8_t);

View File

@ -0,0 +1,533 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include <log/LogUtils.h>
#include "./GroupAssignmentManager.h"
#include "./GroupManager.h"
#include "../vserver/VirtualServer.h"
#include "../client/ConnectedClient.h"
using namespace ts::server::groups;
GroupAssignmentManager::GroupAssignmentManager(GroupManager* handle) : manager_{handle} { }
GroupAssignmentManager::~GroupAssignmentManager() = default;
bool GroupAssignmentManager::initialize(std::string &error) {
return true;
}
sql::SqlManager* GroupAssignmentManager::sql_manager() {
return this->manager_->virtual_server()->sql_manager();
}
ts::ServerId GroupAssignmentManager::server_id() {
return this->manager_->virtual_server()->server_id();
}
bool GroupAssignmentManager::load_data(std::string &error) {
if constexpr(kCacheAllClients) {
std::lock_guard cache_lock{this->client_cache_lock};
std::unique_ptr<ClientCache> current_entry{nullptr};
auto res = sql::command(this->sql_manager(), "SELECT `groupId`, `cldbid`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid ORDER BY `cldbid`", variable{":sid", this->server_id()})
.query([&](int length, std::string* value, std::string* column) {
ChannelId channel_id{0};
GroupId group_id{0};
ClientDbId client_dbid{0};
for(int index = 0; index < length; index++){
try {
if(column[index] == "groupId"){
group_id = stoll(value[index]);
} else if(column[index] == "until"){
} else if(column[index] == "channelId"){
channel_id = stoll(value[index]);
} else if(column[index] == "cldbid"){
client_dbid = stoll(value[index]);
}
} catch(std::exception& ex) {
logError(this->server_id(), "Failed to load group assignment from database. Column {} contains an invalid value: {}", column[index], value[index]);
return 0;
}
}
if(current_entry)
if(current_entry->client_database_id != client_dbid)
this->client_cache.push_back(std::move(current_entry));
if(!current_entry) {
current_entry = std::make_unique<ClientCache>();
current_entry->client_database_id = client_dbid;
}
if(channel_id)
current_entry->channel_group_assignments.emplace_back(channel_id, group_id, false);
else
current_entry->server_group_assignments.emplace_back(group_id);
return 0;
});
if(!res) {
error = "failed to query database (" + res.fmtStr() + ")";
return false;
}
if(current_entry)
this->client_cache.push_back(std::move(current_entry));
}
return true;
}
void GroupAssignmentManager::unload_data() {
std::lock_guard cache_lock{this->client_cache_lock};
this->client_cache.clear();
}
void GroupAssignmentManager::enable_cache_for_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) {
if constexpr(!kCacheAllClients) {
bool cache_exists{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& client : this->client_cache)
if(client->client_database_id == cldbid) {
client->use_count++;
cache_exists = true;
break;
}
}
if(!cache_exists) {
auto cache = std::make_unique<ClientCache>();
cache->client_database_id = cldbid;
cache->use_count++;
auto res = sql::command(this->sql_manager(), "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->server_id()}, variable{":cldbid", cldbid})
.query([&](int length, std::string* value, std::string* column) {
ChannelId channel_id{0};
GroupId group_id{0};
for(int index = 0; index < length; index++){
try {
if(column[index] == "groupId"){
group_id = stoll(value[index]);
} else if(column[index] == "until"){
} else if(column[index] == "channelId"){
channel_id = stoll(value[index]);
}
} catch(std::exception& ex) {
logError(this->server_id(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", cldbid, column[index], value[index]);
return 0;
}
}
if(!group_id)
return 0;
if(channel_id)
cache->channel_group_assignments.emplace_back(channel_id, group_id, false);
else
cache->server_group_assignments.emplace_back(group_id);
return 0;
});
std::lock_guard cache_lock{this->client_cache_lock};
#if 0 /* lets have some performance over double entries :D */
for(auto& client : this->client_cache)
if(client->client_database_id == cldbid) {
/* somebody already inserted that client while we've loaded him */
cache_exists = true;
break;
}
if(!cache_exists)
#endif
this->client_cache.push_back(std::move(cache));
}
}
if(mode == GroupAssignmentCalculateMode::GLOBAL)
if(auto parent = this->manager_->parent_manager(); parent)
parent->assignments().enable_cache_for_client(mode, cldbid);
}
void GroupAssignmentManager::disable_cache_for_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) {
if constexpr(!kCacheAllClients) {
std::lock_guard cache_lock{this->client_cache_lock};
this->client_cache.erase(std::remove_if(this->client_cache.begin(), this->client_cache.end(), [cldbid](const std::unique_ptr<ClientCache>& client) {
return client->client_database_id == cldbid;
}), this->client_cache.end());
}
if(mode == GroupAssignmentCalculateMode::GLOBAL)
if(auto parent = this->manager_->parent_manager(); parent)
parent->assignments().disable_cache_for_client(mode, cldbid);
}
std::vector<ts::GroupId> GroupAssignmentManager::server_groups_of_client(ts::server::groups::GroupAssignmentCalculateMode mode,
ts::ClientDbId cldbid) {
std::vector<ts::GroupId> result{};
bool cache_found{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& entry : this->client_cache) {
if(entry->client_database_id != cldbid) continue;
result.reserve(entry->server_group_assignments.size());
for(auto& assignment : entry->server_group_assignments)
result.push_back(assignment.group_id);
cache_found = true;
break;
}
}
if(!cache_found && !kCacheAllClients) {
debugMessage(this->server_id(), "Query client groups for client {} on server {}.", cldbid, this->server_id());
result.reserve(64);
auto res = sql::command(this->sql_manager(), "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->server_id()}, variable{":cldbid", cldbid})
.query([&](int length, std::string* value, std::string* column) {
GroupId group_id{0};
try {
for(int index = 0; index < length; index++) {
if(column[index] == "groupId") {
group_id = std::stoull(value[index]);
break;
}
}
} catch(std::exception& ex) {
logWarning(this->server_id(), "Invalid data found in group assignment table. Failed to parse group id.");
return 0;
}
if(!group_id) return 0;
result.push_back(group_id);
return 0;
});
LOG_SQL_CMD(res);
}
if(mode == GroupAssignmentCalculateMode::GLOBAL)
if(auto parent = this->manager_->parent_manager(); parent) {
auto parent_groups = parent->assignments().server_groups_of_client(mode, cldbid);
result.reserve(result.size() + parent_groups.size());
result.insert(result.begin(), parent_groups.begin(), parent_groups.end());
}
return result;
}
std::vector<ChannelGroupAssignment> GroupAssignmentManager::channel_groups_of_client(GroupAssignmentCalculateMode mode, ClientDbId cldbid) {
std::vector<ChannelGroupAssignment> result{};
bool cache_found{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& entry : this->client_cache) {
if(entry->client_database_id != cldbid) continue;
result.reserve(entry->channel_group_assignments.size());
result.insert(result.begin(), entry->channel_group_assignments.begin(), entry->channel_group_assignments.end());
cache_found = true;
break;
}
}
if(!cache_found && !kCacheAllClients) {
debugMessage(this->server_id(), "Query client groups for client {} on server {}.", cldbid, this->server_id());
result.reserve(64);
auto res = sql::command(this->sql_manager(), "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` != 0", variable{":sid", this->server_id()}, variable{":cldbid", cldbid})
.query([&](int length, std::string* value, std::string* column) {
GroupId group_id{0};
ChannelId channel_id{0};
try {
for(int index = 0; index < length; index++) {
if(column[index] == "groupId") {
group_id = std::stoull(value[index]);
} else if(column[index] == "channelId") {
channel_id = std::stoull(value[index]);
}
}
} catch(std::exception& ex) {
logWarning(this->server_id(), "Invalid data found in group assignment table. Failed to parse group or channel id.");
return 0;
}
if(!group_id || !channel_id) return 0;
result.emplace_back(channel_id, group_id, false);
return 0;
});
LOG_SQL_CMD(res);
}
if(mode == GroupAssignmentCalculateMode::GLOBAL)
if(auto parent = this->manager_->parent_manager(); parent) {
auto parent_groups = parent->assignments().channel_groups_of_client(mode, cldbid);
result.reserve(result.size() + parent_groups.size());
result.insert(result.begin(), parent_groups.begin(), parent_groups.end());
}
return result;
}
std::deque<ts::ClientDbId> GroupAssignmentManager::server_group_clients(GroupId group_id) {
std::deque<ts::ClientDbId> result{};
if constexpr(kCacheAllClients) {
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& client : this->client_cache) {
auto it = std::find_if(client->server_group_assignments.begin(), client->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) {
return assignment.group_id == group_id;
});
if(it == client->server_group_assignments.end())
continue;
result.push_back(client->client_database_id);
}
} else {
auto res = sql::command(this->sql_manager(), "SELECT `cldbid` FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = 0 AND `groupId` = :gid", variable{":sid", this->server_id()}, variable{":gid", group_id})
.query([&](int length, std::string* value, std::string* column) {
ClientDbId cldbid{0};
try {
for(int index = 0; index < length; index++) {
if(column[index] == "cldbid") {
cldbid = std::stoull(value[index]);
break;
}
}
} catch(std::exception& ex) {
logWarning(this->server_id(), "Invalid data found in group assignment table. Failed to parse client database id.");
return 0;
}
if(!cldbid) return 0;
result.push_back(cldbid);
return 0;
});
LOG_SQL_CMD(res);
}
if(auto parent = this->manager_->parent_manager(); parent) {
auto parent_clients = parent->assignments().server_group_clients(group_id);
result.insert(result.begin(), parent_clients.begin(), parent_clients.end());
}
return result;
}
GroupAssignmentResult GroupAssignmentManager::add_server_group(ClientDbId client, GroupId group) {
bool cache_verified{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& entry : this->client_cache) {
if(entry->client_database_id != client) continue;
auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) {
return assignment.group_id == group;
});
if(it != entry->server_group_assignments.end())
return GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP;
entry->server_group_assignments.emplace_back(group);
cache_verified = true;
break;
}
if(!cache_verified && kCacheAllClients) {
/* add the client to the cache */
auto cache = std::make_unique<ClientCache>();
cache->client_database_id = client;
cache->server_group_assignments.emplace_back(group);
this->client_cache.push_back(std::move(cache));
}
}
{
auto command = sql::command(this->sql_manager(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->server_id()},
variable{":cldbid", client},
variable{":gid", group},
variable{":chid", 0},
variable{":until", 0});
if(cache_verified)
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to insert group assignment into the database"});
else {
auto result = command.execute();
if(!result) return GroupAssignmentResult::ADD_ALREADY_MEMBER_OF_GROUP; //TODO: Parse error from database?
}
}
return GroupAssignmentResult::SUCCESS;
}
GroupAssignmentResult GroupAssignmentManager::remove_server_group(ClientDbId client, GroupId group) {
bool cache_verified{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& entry : this->client_cache) {
if(entry->client_database_id != client) continue;
auto it = std::find_if(entry->server_group_assignments.begin(), entry->server_group_assignments.end(), [&](const ServerGroupAssignment& assignment) {
return assignment.group_id == group;
});
if(it == entry->server_group_assignments.end())
return GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP;
entry->server_group_assignments.erase(it);
cache_verified = true;
break;
}
if(!cache_verified && kCacheAllClients)
return GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP;
}
{
auto command = sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid",
variable{":sid", this->server_id()},
variable{":cldbid", client},
variable{":gid", group},
variable{":chid", 0});
if(cache_verified)
command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to remove group assignment from database"});
else {
auto result = command.execute();
if(!result) return GroupAssignmentResult::REMOVE_NOT_MEMBER_OF_GROUP; //TODO: Parse error from database?
}
}
return GroupAssignmentResult::SUCCESS;
}
GroupAssignmentResult GroupAssignmentManager::set_channel_group(ClientDbId client, GroupId group, ChannelId channel_id, bool temporary) {
bool cache_verified{false};
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& entry : this->client_cache) {
if(entry->client_database_id != client) continue;
auto it = std::find_if(entry->channel_group_assignments.begin(), entry->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) {
return assignment.channel_id == channel_id;
});
if(it != entry->channel_group_assignments.end()) {
if(group) {
if(it->group_id == group)
return GroupAssignmentResult::SET_ALREADY_MEMBER_OF_GROUP;
it->group_id = group;
} else {
entry->channel_group_assignments.erase(it);
}
} else {
if(group) {
entry->channel_group_assignments.emplace_back(channel_id, group, temporary);
}
}
cache_verified = true;
break;
}
if(!cache_verified && kCacheAllClients) {
if(group) {
/* add the client to the cache */
auto cache = std::make_unique<ClientCache>();
cache->client_database_id = client;
cache->channel_group_assignments.emplace_back(channel_id, group, temporary);
this->client_cache.push_back(std::move(cache));
} else {
return GroupAssignmentResult::SUCCESS;
}
}
}
if(temporary)
return GroupAssignmentResult::SUCCESS;
sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid",
variable{":sid", this->server_id()},
variable{":cldbid", client},
variable{":chid", channel_id})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "failed to delete old channel group assignment"});
if(group) {
sql::command(this->sql_manager(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->server_id()},
variable{":cldbid", client},
variable{":gid", group},
variable{":chid", channel_id},
variable{":until", 0})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "failed to insert channel group assignment"});
}
return GroupAssignmentResult::SUCCESS;
}
void GroupAssignmentManager::cleanup_channel_assignments(ChannelId channel_id) {
{
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& client : this->client_cache) {
client->channel_group_assignments.erase(std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) {
return assignment.channel_id == channel_id;
}));
}
}
sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `channelId` = :cid", variable{":sid", this->server_id()}, variable{":cid", channel_id})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupAssignmentManager::cleanup_assignments() {
{
std::lock_guard cache_lock{this->client_cache_lock};
this->client_cache.clear();
}
sql::command(this->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->server_id()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupAssignmentManager::cleanup_channel_temporary_assignment(ClientDbId client_dbid, ChannelId channel) {
std::lock_guard cache_lock{this->client_cache_lock};
for(auto& client : this->client_cache) {
if(client->client_database_id == client_dbid) {
auto assignment = std::find_if(client->channel_group_assignments.begin(), client->channel_group_assignments.end(), [&](const ChannelGroupAssignment& assignment) {
return assignment.channel_id == channel;
});
if(assignment->temporary_assignment)
client->channel_group_assignments.erase(assignment);
break;
}
}
}
std::deque<ts::property::ClientProperties> GroupAssignmentManager::update_client_group_properties(const std::shared_ptr<server::ConnectedClient> &client, ChannelId channel_id) {
std::deque<property::ClientProperties> changed;
bool increase_join_state{false};
//Server groups
{
auto server_groups = this->server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, client->getClientDatabaseId());
std::string group_string;
for(const auto& group : server_groups)
if(group_string.empty()) group_string += std::to_string(group);
else group_string += "," + std::to_string(group);
if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) {
client->properties()[property::CLIENT_SERVERGROUPS] = group_string;
changed.push_back(property::CLIENT_SERVERGROUPS);
increase_join_state = true;
}
}
//Channel group
if(channel_id) {
auto assignment = this->manager_->virtual_server()->channel_service().calculate_channel_group(channel_id, client->getClientDatabaseId(), client->getType());
if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != assignment.inherited_channel_id) {
client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = assignment.inherited_channel_id;
changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID);
}
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != assignment.group_id) {
client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = assignment.group_id;
changed.push_back(property::CLIENT_CHANNEL_GROUP_ID);
increase_join_state = true;
}
}
if(increase_join_state)
client->increase_join_state(); /* groups have changed :) */
return changed;
}

View File

@ -0,0 +1,112 @@
#pragma once
#include <mutex>
#include <deque>
#include <string>
#include <memory>
#include <utility>
#include <Definitions.h>
#include <Properties.h>
namespace sql {
class SqlManager;
}
namespace ts::server {
class ConnectedClient;
namespace vserver {
class VirtualServerBase;
}
namespace groups {
enum struct GroupAssignmentCalculateMode {
LOCAL, /* only calculate clients groups for the local server */
GLOBAL /* use the parent group manager as well, if existing */
};
class ServerGroup;
class ChannelGroup;
class GroupManager;
struct ChannelGroupAssignment {
ChannelGroupAssignment(ChannelId channel_id, GroupId group_id, bool t) : channel_id{channel_id}, group_id{group_id}, temporary_assignment{t} { }
ChannelGroupAssignment(const ChannelGroupAssignment& other) = default;
ChannelGroupAssignment(ChannelGroupAssignment&&) = default;
ChannelGroupAssignment&operator=(const ChannelGroupAssignment&) = default;
ChannelId channel_id;
GroupId group_id;
bool temporary_assignment;
};
struct ServerGroupAssignment {
explicit ServerGroupAssignment(GroupId group_id) : group_id{group_id} { }
ServerGroupAssignment(const ServerGroupAssignment& other) = default;
ServerGroupAssignment(ServerGroupAssignment&&) = default;
ServerGroupAssignment&operator=(const ServerGroupAssignment&) = default;
GroupId group_id;
};
enum struct GroupAssignmentResult {
SUCCESS,
ADD_ALREADY_MEMBER_OF_GROUP,
REMOVE_NOT_MEMBER_OF_GROUP,
SET_ALREADY_MEMBER_OF_GROUP
};
class GroupAssignmentManager {
constexpr static bool kCacheAllClients{true};
public:
explicit GroupAssignmentManager(GroupManager* /* manager */);
~GroupAssignmentManager();
/* general load/initialize methods */
bool initialize(std::string& /* error */);
bool load_data(std::string& /* error */);
void unload_data();
/* client specific cache methods */
void enable_cache_for_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */);
void disable_cache_for_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */);
/* info/query methods */
[[nodiscard]] std::vector<GroupId> server_groups_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */);
[[nodiscard]] std::vector<ChannelGroupAssignment> channel_groups_of_client(GroupAssignmentCalculateMode /* mode */, ClientDbId /* client database id */);
[[nodiscard]] std::deque<ClientDbId> server_group_clients(GroupId /* group id */);
//[[nodiscard]] std::deque<ClientDbId> channel_group_clients(GroupId /* group id */, ChannelId /* channel id */);
/* change methods */
GroupAssignmentResult add_server_group(ClientDbId /* client database id */, GroupId /* group id */);
GroupAssignmentResult remove_server_group(ClientDbId /* client database id */, GroupId /* group id */);
/* Use channel group id 0 to delete any assignment */
GroupAssignmentResult set_channel_group(ClientDbId /* client database id */, GroupId /* group id */, ChannelId /* channel id */, bool /* temporary assignment */);
std::deque<property::ClientProperties> update_client_group_properties(const std::shared_ptr<ConnectedClient> &client, ChannelId /* target channel */);
void cleanup_assignments();
void cleanup_channel_assignments(ChannelId /* channel */);
void cleanup_channel_temporary_assignment(ClientDbId /* client database id */, ChannelId /* channel */);
private:
struct ClientCache {
ClientDbId client_database_id{0};
size_t use_count{0};
std::deque<ChannelGroupAssignment> channel_group_assignments{};
std::deque<ServerGroupAssignment> server_group_assignments{};
};
GroupManager* manager_;
std::mutex client_cache_lock;
std::deque<std::unique_ptr<ClientCache>> client_cache{};
[[nodiscard]] sql::SqlManager* sql_manager();
[[nodiscard]] ServerId server_id();
};
}
}

View File

@ -0,0 +1,612 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include <log/LogUtils.h>
#include "GroupManager.h"
#include "../vserver/VirtualServerBase.h"
#include "../InstanceHandler.h"
using namespace ts::server::groups;
GroupManager::GroupManager(ts::server::vserver::VirtualServerBase *server, std::shared_ptr<GroupManager> parent)
: virtual_server_{server}, parent_manager_{std::move(parent)}, assignment_manager_{this} {
assert(server);
}
GroupManager::~GroupManager() = default;
ts::ServerId GroupManager::server_id() {
return this->virtual_server_->server_id();
}
sql::SqlManager* GroupManager::sql_manager() {
return this->virtual_server_->sql_manager();
}
bool GroupManager::initialize(const std::shared_ptr<GroupManager>& self, std::string &error) {
assert(&*self == this);
this->weak_ref_ = self;
return this->assignment_manager_.initialize(error);
}
GroupLoadResult GroupManager::load_data(bool initialize) {
std::lock_guard manage_lock{this->group_manage_lock_};
{
std::lock_guard list_lock{this->group_lock_};
this->server_groups_.clear();
this->channel_groups_.clear();
}
auto res = sql::command(this->sql_manager(), "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->server_id()}).query(&GroupManager::insert_group_from_sql, this);
if(!res) {
LOG_SQL_CMD(res);
return GroupLoadResult::DATABASE_ERROR;
}
if(this->server_groups_.empty() || this->channel_groups_.empty())
return GroupLoadResult::NO_GROUPS;
this->ensure_valid_default_groups(initialize);
return GroupLoadResult::SUCCESS;
}
void GroupManager::unload_data() {
std::lock_guard manage_lock{this->group_manage_lock_};
{
std::lock_guard list_lock{this->group_lock_};
this->server_groups_.clear();
this->channel_groups_.clear();
}
}
void GroupManager::reset_groups(bool db_cleanup) {
std::lock_guard manage_lock{this->group_manage_lock_};
this->unload_data();
if(db_cleanup) {
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type",
variable{":serverId", this->server_id()},
variable{":type", ts::permission::SQL_PERM_GROUP}).execute());
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->server_id()}).execute());
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->server_id()}).execute());
}
if(auto error = this->load_data(true); error != GroupLoadResult::SUCCESS)
logCritical(this->server_id(), "Failed to load groups after group unload ({}). There might be no groups loaded now!", (int) error);
}
int GroupManager::insert_group_from_sql(int length, std::string *values, std::string *names) {
GroupId group_id{0};
GroupType group_type{GroupType::GROUP_TYPE_UNKNOWN};
std::string group_name{};
GroupTarget group_target{GroupTarget::GROUPTARGET_UNKNOWN};
for(size_t index = 0; index < length; index++) {
try {
if(names[index] == "groupId")
group_id = std::stoull(values[index]);
else if(names[index] == "target")
group_target = (GroupTarget) std::stoull(values[index]);
else if(names[index] == "type")
group_type = (GroupType) std::stoull(values[index]);
else if(names[index] == "displayName")
group_name = names[index];
} catch(std::exception& ex) {
logWarning(this->server_id(), "Failed to parse group from database. Failed to parse column {} (value: {})", names[index], values[index]);
return 0;
}
}
if(!group_id || group_type == GroupType::GROUP_TYPE_UNKNOWN || group_target == GroupTarget::GROUPTARGET_UNKNOWN) {
logWarning(this->server_id(), "Failed to query group from database. Invalid values found.");
return 0;
}
auto database_target = (uint8_t) (group_target == GroupTarget::GROUPTARGET_SERVER ? DatabaseGroupTarget::SERVER : DatabaseGroupTarget::CHANNEL);
auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, database_target);
if(group_target == GroupTarget::GROUPTARGET_SERVER) {
auto group = std::make_shared<ServerGroup>(this->server_id(), group_id, group_type, group_name, permissions);
std::lock_guard lock{this->group_lock_};
this->server_groups_.push_back(group);
} else {
auto group = std::make_shared<ChannelGroup>(this->server_id(), group_id, group_type, group_name, permissions);
std::lock_guard lock{this->group_lock_};
this->channel_groups_.push_back(group);
}
return 0;
}
void GroupManager::ensure_valid_default_groups(bool initialize) {
assert(!this->channel_groups_.empty() && !this->server_groups_.empty());
auto fallback_server_group = this->server_groups_.front();
auto fallback_channel_group = this->channel_groups_.front();
auto properties = this->virtual_server_->server_properties();
/* default server music group */
{
auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP).as_save<GroupId>();
auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
if(!initialize)
logWarning(this->server_id(), "Missing default music bot group ({}). Try to reinitialize from serverinstance_template_musicdefault_group.", group_id);
group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>();
group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
group = fallback_server_group;
logCritical(LOG_INSTANCE, "Failed to find default music bot group ({}). Using first available group ({}).", group_id, fallback_server_group->group_id());
}
auto group_name = group->display_name();
group = this->find_server_group_by_name(GroupCalculateMode::LOCAL, group_name);
if(!group) {
group = fallback_server_group;
logError(this->server_id(), "Missing default music bot group and could not find group from template name ({}). Using {} as fallback.", group_name, group);
}
properties->find(property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP) = group->group_id();
}
}
/* default server guest group */
{
auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP).as_save<GroupId>();
auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
if(!initialize)
logWarning(this->server_id(), "Missing default server guest group ({}). Try to reinitialize from serverinstance_template_serverdefault_group.", group_id);
group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as<GroupId>();
group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
group = fallback_server_group;
logCritical(LOG_INSTANCE, "Failed to find default server guest group ({}). Using first available group ({}).", group_id, fallback_server_group->group_id());
}
auto group_name = group->display_name();
group = this->find_server_group_by_name(GroupCalculateMode::LOCAL, group_name);
if(!group) {
group = fallback_server_group;
logError(this->server_id(), "Missing default server guest group and could not find group from template name ({}). Using {} as fallback.", group_name, group);
}
properties->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP) = group->group_id();
}
}
/* default channel admin group */
{
auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP).as_save<GroupId>();
auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
if(!initialize)
logWarning(this->server_id(), "Missing default channel admin group ({}). Try to reinitialize from serverinstance_template_channeladmin_group.", group_id);
group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as<GroupId>();
group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
group = fallback_channel_group;
logCritical(LOG_INSTANCE, "Failed to find default channel admin group ({}). Using first available group ({}).", group_id, fallback_channel_group->group_id());
}
auto group_name = group->display_name();
group = this->find_channel_group_by_name(GroupCalculateMode::LOCAL, group_name);
if(!group) {
group = fallback_channel_group;
logError(this->server_id(), "Missing default channel admin group and could not find group from template name ({}). Using {} as fallback.", group_name, group);
}
properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP) = group->group_id();
}
}
/* default channel group */
{
auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP).as_save<GroupId>();
auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
if(!initialize)
logWarning(this->server_id(), "Missing default channel guest group ({}). Try to reinitialize from serverinstance_template_channeldefault_group.", group_id);
group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as<GroupId>();
group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
group = fallback_channel_group;
logCritical(LOG_INSTANCE, "Failed to find default channel guest group ({}). Using first available group ({}).", group_id, fallback_channel_group->group_id());
}
auto group_name = group->display_name();
group = this->find_channel_group_by_name(GroupCalculateMode::LOCAL, group_name);
if(!group) {
group = fallback_channel_group;
logError(this->server_id(), "Missing default channel guest group and could not find group from template name ({}). Using {} as fallback.", group_name, group);
}
properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP) = group->group_id();
}
}
}
std::shared_ptr<ServerGroup> GroupManager::default_server_group(ClientType client_type, bool enforce_id) {
auto id =
client_type != ClientType::CLIENT_QUERY ?
this->virtual_server_->server_properties()->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP).as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, id);
if(group || enforce_id) return group;
std::lock_guard glock{this->group_lock_};
return this->server_groups_.empty() ? nullptr : this->server_groups_.front();
}
std::shared_ptr<ChannelGroup> GroupManager::default_channel_group(ClientType client_type, bool enforce_id) {
auto id = this->virtual_server_->server_properties()->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP).as_save<GroupId>();
auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, id);
if(group || enforce_id) return group;
std::lock_guard glock{this->group_lock_};
return this->channel_groups_.empty() ? nullptr : this->channel_groups_.front();
}
std::shared_ptr<ServerGroup> GroupManager::find_server_group(GroupCalculateMode mode, GroupId group_id) {
{
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.end(), [&](const std::shared_ptr<ServerGroup>& group) {
return group->group_id() == group_id;
});
if(it != this->server_groups_.end())
return *it;
}
if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_)
return this->parent_manager_->find_server_group(mode, group_id);
return nullptr;
}
std::shared_ptr<ServerGroup> GroupManager::find_server_group_by_name(GroupCalculateMode mode, const std::string &name) {
{
std::string lname{name};
std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower);
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.end(),
[&](const std::shared_ptr<ServerGroup> &group) {
std::string lgroup_name{group->name_};
std::transform(lgroup_name.begin(), lgroup_name.end(), lgroup_name.begin(),
::tolower);
return lname == lgroup_name;
});
if(it != this->server_groups_.end())
return *it;
}
if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_)
return this->parent_manager_->find_server_group_by_name(mode, name);
return nullptr;
}
std::shared_ptr<ChannelGroup> GroupManager::find_channel_group(GroupCalculateMode mode, GroupId group_id) {
{
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.end(), [&](const std::shared_ptr<ChannelGroup>& group) {
return group->group_id() == group_id;
});
if(it != this->channel_groups_.end())
return *it;
}
if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_)
return this->parent_manager_->find_channel_group(mode, group_id);
return nullptr;
}
std::shared_ptr<ChannelGroup> GroupManager::find_channel_group_by_name(GroupCalculateMode mode, const std::string &name) {
{
std::string lname{name};
std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower);
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.end(),
[&](const std::shared_ptr<ChannelGroup> &group) {
std::string lgroup_name{group->name_};
std::transform(lgroup_name.begin(), lgroup_name.end(), lgroup_name.begin(),
::tolower);
return lname == lgroup_name;
});
if(it != this->channel_groups_.end())
return *it;
}
if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_)
return this->parent_manager_->find_channel_group_by_name(mode, name);
return nullptr;
}
GroupCreateResult GroupManager::create_server_group(GroupType type, const std::string &name, std::shared_ptr<ServerGroup>& result) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCreateResult::DATABASE_ERROR;
if(this->find_server_group_by_name(GroupCalculateMode::LOCAL, name))
return GroupCreateResult::NAME_ALREADY_IN_USED;
auto group_id = this->generate_server_group_id();
if(!group_id)
return GroupCreateResult::FAILED_TO_GENERATE_ID;
auto res = sql::command(this->sql_manager(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", this->server_id()}, variable{":gid", group_id}, variable{":target", DatabaseGroupTarget::SERVER}, variable{":type", type}, variable{":name", name}).execute();
if(!res) {
LOG_SQL_CMD(res);
return GroupCreateResult::DATABASE_ERROR;
}
auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, (uint8_t) DatabaseGroupTarget::SERVER);
auto group = std::make_shared<ServerGroup>(self->server_id(), group_id, type, name, permissions);
{
std::lock_guard glock{this->group_lock_};
this->server_groups_.push_back(group);
}
result = group;
return GroupCreateResult::SUCCESS;
}
GroupCreateResult GroupManager::create_channel_group(GroupType type, const std::string &name, std::shared_ptr<ChannelGroup> &result) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCreateResult::DATABASE_ERROR;
if(this->find_channel_group_by_name(GroupCalculateMode::LOCAL, name))
return GroupCreateResult::NAME_ALREADY_IN_USED;
auto group_id = this->generate_channel_group_id();
if(!group_id)
return GroupCreateResult::FAILED_TO_GENERATE_ID;
auto res = sql::command(this->sql_manager(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", this->server_id()}, variable{":gid", group_id}, variable{":target", DatabaseGroupTarget::CHANNEL}, variable{":type", type}, variable{":name", name}).execute();
if(!res) {
LOG_SQL_CMD(res);
return GroupCreateResult::DATABASE_ERROR;
}
auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, (uint8_t) DatabaseGroupTarget::CHANNEL);
auto group = std::make_shared<ChannelGroup>(this->server_id(), group_id, type, name, permissions);
{
std::lock_guard glock{this->group_lock_};
this->channel_groups_.push_back(group);
}
result = group;
return GroupCreateResult::SUCCESS;
}
GroupCopyResult GroupManager::copy_server_group(GroupId source, GroupType target_type, const std::string &display_name, std::shared_ptr<ServerGroup>& result) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCopyResult::DATABASE_ERROR;
std::shared_ptr<ServerGroup> group{};
auto create_result = this->create_server_group(target_type, display_name, group);
switch(create_result) {
case GroupCreateResult::SUCCESS:
break;
case GroupCreateResult::DATABASE_ERROR:
return GroupCopyResult::DATABASE_ERROR;
case GroupCreateResult::NAME_ALREADY_IN_USED:
return GroupCopyResult::NAME_ALREADY_IN_USE;
case GroupCreateResult::FAILED_TO_GENERATE_ID:
return GroupCopyResult::FAILED_TO_GENERATE_ID;
}
assert(group);
return this->copy_server_group_permissions(source, group->group_id());
}
GroupCopyResult GroupManager::copy_channel_group(GroupId source, GroupType target_type, const std::string &display_name, std::shared_ptr<ChannelGroup> &result) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCopyResult::DATABASE_ERROR;
std::shared_ptr<ChannelGroup> group{};
auto create_result = this->create_channel_group(target_type, display_name, group);
switch(create_result) {
case GroupCreateResult::SUCCESS:
break;
case GroupCreateResult::DATABASE_ERROR:
return GroupCopyResult::DATABASE_ERROR;
case GroupCreateResult::NAME_ALREADY_IN_USED:
return GroupCopyResult::NAME_ALREADY_IN_USE;
case GroupCreateResult::FAILED_TO_GENERATE_ID:
return GroupCopyResult::FAILED_TO_GENERATE_ID;
}
assert(group);
return this->copy_channel_group_permissions(source, group->group_id());
}
GroupCopyResult GroupManager::copy_server_group_permissions(GroupId source, GroupId target) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCopyResult::DATABASE_ERROR;
auto source_group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, source);
if(!source_group)
return GroupCopyResult::UNKNOWN_SOURCE_GROUP;
auto target_group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, target);
if(!target_group)
return GroupCopyResult::UNKNOWN_TARGET_GROUP;
auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id",
variable{":sid", this->server_id()}, variable{":type", ts::permission::SQL_PERM_GROUP}, variable{":id", target}).execute();
if(!res) {
LOG_SQL_CMD(res);
return GroupCopyResult::DATABASE_ERROR;
}
res = sql::command(this->sql_manager(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", source_group->virtual_server_id()},
variable{":tsid", target_group->virtual_server_id()},
variable{":type", ts::permission::SQL_PERM_GROUP},
variable{":source", source},
variable{":target", target}).execute();
target_group->set_permissions(serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), target, (uint8_t) DatabaseGroupTarget::SERVER));
LOG_SQL_CMD(res);
return GroupCopyResult::SUCCESS;
}
GroupCopyResult GroupManager::copy_channel_group_permissions(GroupId source, GroupId target) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupCopyResult::DATABASE_ERROR;
auto source_group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, source);
if(!source_group)
return GroupCopyResult::UNKNOWN_SOURCE_GROUP;
auto target_group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, target);
if(!target_group)
return GroupCopyResult::UNKNOWN_TARGET_GROUP;
auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id",
variable{":sid", this->server_id()}, variable{":type", ts::permission::SQL_PERM_GROUP}, variable{":id", target}).execute();
if(!res) {
LOG_SQL_CMD(res);
return GroupCopyResult::DATABASE_ERROR;
}
res = sql::command(this->sql_manager(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", source_group->virtual_server_id()},
variable{":tsid", target_group->virtual_server_id()},
variable{":type", ts::permission::SQL_PERM_GROUP},
variable{":source", source},
variable{":target", target}).execute();
target_group->set_permissions(serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), target, (uint8_t) DatabaseGroupTarget::CHANNEL));
LOG_SQL_CMD(res);
return GroupCopyResult::SUCCESS;
}
GroupRenameResult GroupManager::rename_server_group(GroupId group_id, const std::string &name) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupRenameResult::DATABASE_ERROR;
if(name.empty() || name.length() > 40)
return GroupRenameResult::NAME_INVALID;
auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) return GroupRenameResult::INVALID_GROUP_ID;
if(this->find_server_group_by_name(GroupCalculateMode::LOCAL, name))
return GroupRenameResult::NAME_ALREADY_USED;
sql::command(this->sql_manager(), "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target",
variable{":server", this->server_id()},
variable{":target", DatabaseGroupTarget::SERVER},
variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"});
group->name_ = name;
return GroupRenameResult::SUCCESS;
}
GroupRenameResult GroupManager::rename_channel_group(GroupId group_id, const std::string &name) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupRenameResult::DATABASE_ERROR;
if(name.empty() || name.length() > 40)
return GroupRenameResult::NAME_INVALID;
auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) return GroupRenameResult::INVALID_GROUP_ID;
if(this->find_channel_group_by_name(GroupCalculateMode::LOCAL, name))
return GroupRenameResult::NAME_ALREADY_USED;
sql::command(this->sql_manager(), "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target",
variable{":server", this->server_id()},
variable{":target", DatabaseGroupTarget::CHANNEL},
variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"});
group->name_ = name;
return GroupRenameResult::SUCCESS;
}
GroupDeleteResult GroupManager::delete_server_group(GroupId group_id) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupDeleteResult::DATABASE_ERROR;
{
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.begin(), [&](const std::shared_ptr<ServerGroup>& group) {
return group->group_id() == group_id;
});
if(it == this->server_groups_.end())
return GroupDeleteResult::INVALID_GROUP_ID;
this->server_groups_.erase(it);
}
sql::command(this->sql_manager(), "DELETE FROM WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target",
variable{":server", this->server_id()},
variable{":target", DatabaseGroupTarget::SERVER},
variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"});
return GroupDeleteResult::SUCCESS;
}
GroupDeleteResult GroupManager::delete_channel_group(GroupId group_id) {
std::lock_guard manage_lock{this->group_manage_lock_};
auto self = this->weak_ref_.lock();
if(!self) return GroupDeleteResult::DATABASE_ERROR;
{
std::lock_guard glock{this->group_lock_};
auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.begin(), [&](const std::shared_ptr<ChannelGroup>& group) {
return group->group_id() == group_id;
});
if(it == this->channel_groups_.end())
return GroupDeleteResult::INVALID_GROUP_ID;
this->channel_groups_.erase(it);
}
sql::command(this->sql_manager(), "DELETE FROM WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target",
variable{":server", this->server_id()},
variable{":target", DatabaseGroupTarget::CHANNEL},
variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"});
return GroupDeleteResult::SUCCESS;
}
ts::GroupId GroupManager::generate_server_group_id() {
std::lock_guard glock{this->group_lock_};
ts::GroupId highest_group_id{0};
for(auto& group : this->channel_groups_)
highest_group_id = std::max(group->group_id(), highest_group_id);
for(auto& group : this->server_groups_)
highest_group_id = std::max(group->group_id(), highest_group_id);
if(this->parent_manager_)
highest_group_id = std::max(this->parent_manager_->generate_server_group_id(), highest_group_id);
return highest_group_id + 1;
}
ts::GroupId GroupManager::generate_channel_group_id() {
return this->generate_server_group_id();
}

View File

@ -0,0 +1,133 @@
#pragma once
#include <mutex>
#include <deque>
#include <string>
#include <memory>
#include <Definitions.h>
#include <sql/SqlQuery.h>
#include "./GroupAssignmentManager.h"
#include "./Group.h"
namespace ts::server {
namespace vserver {
class VirtualServerBase;
}
namespace groups {
enum struct GroupCalculateMode {
LOCAL, /* only calculate clients groups for the local server */
GLOBAL /* use the parent group manager as well, if existing */
};
enum struct GroupLoadResult {
SUCCESS,
NO_GROUPS,
DATABASE_ERROR
};
enum struct GroupCreateResult {
SUCCESS,
NAME_ALREADY_IN_USED,
FAILED_TO_GENERATE_ID,
DATABASE_ERROR
};
enum struct GroupCopyResult {
SUCCESS,
UNKNOWN_SOURCE_GROUP,
UNKNOWN_TARGET_GROUP,
NAME_ALREADY_IN_USE,
FAILED_TO_GENERATE_ID,
DATABASE_ERROR
};
enum struct GroupRenameResult {
SUCCESS,
INVALID_GROUP_ID,
NAME_ALREADY_USED,
NAME_INVALID,
DATABASE_ERROR
};
enum struct GroupDeleteResult {
SUCCESS,
INVALID_GROUP_ID,
DATABASE_ERROR
};
class GroupManager {
friend class Group;
public:
enum struct DatabaseGroupTarget {
SERVER,
CHANNEL
};
GroupManager(vserver::VirtualServerBase* /* virtual server */, std::shared_ptr<GroupManager> /* parent */);
~GroupManager();
bool initialize(const std::shared_ptr<GroupManager>& /* self ref, */, std::string& /* error */);
GroupLoadResult load_data(bool /* initialize */ = false);
void unload_data();
void reset_groups(bool /* cleanup database */);
[[nodiscard]] inline vserver::VirtualServerBase* virtual_server() { return this->virtual_server_; }
[[nodiscard]] inline const std::shared_ptr<GroupManager>& parent_manager() { return this->parent_manager_; }
[[nodiscard]] inline GroupAssignmentManager& assignments() { return this->assignment_manager_; }
[[nodiscard]] std::vector<std::shared_ptr<ServerGroup>> server_groups(GroupCalculateMode /* mode */);
[[nodiscard]] std::vector<std::shared_ptr<ChannelGroup>> channel_groups(GroupCalculateMode /* mode */);
[[nodiscard]] std::shared_ptr<ServerGroup> default_server_group(ClientType /* client type */, bool /* enforce id */ = false);
[[nodiscard]] std::shared_ptr<ChannelGroup> default_channel_group(ClientType /* client type */, bool /* enforce id */ = false);
[[nodiscard]] std::shared_ptr<ServerGroup> find_server_group(GroupCalculateMode /* mode */, GroupId /* group id */);
[[nodiscard]] std::shared_ptr<ChannelGroup> find_channel_group(GroupCalculateMode /* mode */, GroupId /* group id */);
[[nodiscard]] std::shared_ptr<ServerGroup> find_server_group_by_name(GroupCalculateMode /* mode */, const std::string& /* group name */);
[[nodiscard]] std::shared_ptr<ChannelGroup> find_channel_group_by_name(GroupCalculateMode /* mode */, const std::string& /* group name */);
[[nodiscard]] GroupCreateResult create_server_group(GroupType type, const std::string& /* group name */, std::shared_ptr<ServerGroup>& /* result */);
[[nodiscard]] GroupCreateResult create_channel_group(GroupType type, const std::string& /* group name */, std::shared_ptr<ChannelGroup>& /* result */);
[[nodiscard]] GroupCopyResult copy_server_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */, std::shared_ptr<ServerGroup>& /* result */);
[[nodiscard]] GroupCopyResult copy_channel_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */, std::shared_ptr<ChannelGroup>& /* result */);
[[nodiscard]] GroupCopyResult copy_server_group_permissions(GroupId /* group id */, GroupId /* target group */);
[[nodiscard]] GroupCopyResult copy_channel_group_permissions(GroupId /* group id */, GroupId /* target group */);
[[nodiscard]] GroupRenameResult rename_server_group(GroupId /* group id */, const std::string& /* target group name */);
[[nodiscard]] GroupRenameResult rename_channel_group(GroupId /* group id */, const std::string& /* target group name */);
[[nodiscard]] GroupDeleteResult delete_server_group(GroupId /* group id */);
[[nodiscard]] GroupDeleteResult delete_channel_group(GroupId /* group id */);
private:
vserver::VirtualServerBase* virtual_server_;
std::weak_ptr<GroupManager> weak_ref_;
std::shared_ptr<GroupManager> parent_manager_;
GroupAssignmentManager assignment_manager_;
/* recursive_mutex due to the copy server/channel group methods */
std::recursive_mutex group_manage_lock_{};
std::mutex group_lock_{};
/* I think std::vector is better here because we will iterate more often than add groups */
std::vector<std::shared_ptr<ServerGroup>> server_groups_{};
std::vector<std::shared_ptr<ChannelGroup>> channel_groups_{};
[[nodiscard]] sql::SqlManager* sql_manager();
[[nodiscard]] ServerId server_id();
void ensure_valid_default_groups(bool /* initialize */);
int insert_group_from_sql(int /* length */, std::string* /* values */, std::string* /* columns */);
[[nodiscard]] GroupId generate_server_group_id();
[[nodiscard]] GroupId generate_channel_group_id();
};
}
}
DEFINE_TRANSFORMS(ts::server::groups::GroupManager::DatabaseGroupTarget, uint8_t);

View File

@ -4,6 +4,7 @@
#include <netdb.h>
#include <cassert>
#include <misc/strobf.h>
#include <misc/net.h>
#include <log/LogUtils.h>
#include <src/Configuration.h>
#include <src/ShutdownHelper.h>

View File

@ -9,6 +9,7 @@ using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::server::bans;
BanManager::BanManager(sql::SqlManager* handle) : sql(handle) {}
@ -274,7 +275,7 @@ void BanManager::trigger_ban(const std::shared_ptr<BanRecord>& record,
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
std::deque<std::shared_ptr<BanTrigger>> BanManager::trigger_list(const std::shared_ptr<ts::server::BanRecord> &record, ServerId server_id, ssize_t offset, ssize_t length) {
std::deque<std::shared_ptr<BanTrigger>> BanManager::trigger_list(const std::shared_ptr<ts::server::bans::BanRecord> &record, ServerId server_id, ssize_t offset, ssize_t length) {
std::deque<std::shared_ptr<BanTrigger>> result;
if(offset < 0) offset = 0;

View File

@ -10,8 +10,7 @@
#include <Definitions.h>
#include <sql/SqlQuery.h>
namespace ts {
namespace server {
namespace ts::server {
enum BanStringType {
BST_WILD_V4,
BST_WILD_V6,
@ -19,91 +18,92 @@ namespace ts {
BST_REGEX
};
}
}
DEFINE_VARIABLE_TRANSFORM(ts::server::BanStringType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast<ts::server::BanStringType>(in.as<uint8_t>()));
namespace ts {
namespace server {
namespace ts::server {
class VirtualServer;
class ConnectedClient;
struct BanRecord {
ServerId serverId;
BanId banId;
namespace bans {
struct BanRecord {
ServerId serverId;
BanId banId;
ClientDbId invokerDbId;
std::string invokerUid;
std::string invokerName;
ClientDbId invokerDbId;
std::string invokerUid;
std::string invokerName;
std::string hwid;
std::string uid;
std::string hwid;
std::string uid;
std::string name;
std::string ip;
BanStringType strType;
std::string name;
std::string ip;
BanStringType strType;
std::chrono::time_point<std::chrono::system_clock> created;
std::chrono::time_point<std::chrono::system_clock> until;
std::chrono::time_point<std::chrono::system_clock> created;
std::chrono::time_point<std::chrono::system_clock> until;
std::string reason;
std::string reason;
size_t triggered;
};
size_t triggered;
};
struct BanTrigger {
ServerId server_id;
BanId ban_id;
std::chrono::system_clock::time_point timestamp;
struct BanTrigger {
ServerId server_id;
BanId ban_id;
std::chrono::system_clock::time_point timestamp;
std::string unique_id;
std::string hardware_id;
std::string name;
std::string ip;
};
std::string unique_id;
std::string hardware_id;
std::string name;
std::string ip;
};
class BanManager {
public:
BanManager(sql::SqlManager*);
~BanManager();
class BanManager {
public:
BanManager(sql::SqlManager*);
BanManager(const BanManager&) = delete;
BanManager(BanManager&&) = delete;
~BanManager();
bool loadBans();
bool loadBans();
std::deque<std::shared_ptr<BanRecord>> listBans(ServerId);
std::deque<std::shared_ptr<BanRecord>> listBans(ServerId);
std::shared_ptr<BanRecord> findBanById(ServerId, uint64_t banId);
std::shared_ptr<BanRecord> findBanByHwid(ServerId, std::string hwid);
std::shared_ptr<BanRecord> findBanByUid(ServerId, std::string uid);
std::shared_ptr<BanRecord> findBanByIp(ServerId, std::string ip);
std::shared_ptr<BanRecord> findBanByName(ServerId, std::string nickName);
std::shared_ptr<BanRecord> findBanExact(ServerId,
const std::string& /* reason */,
const std::string& /* uid */,
const std::string& /* ip */,
const std::string& /* display name */,
const std::string& /* hardware id */);
std::shared_ptr<BanRecord> findBanById(ServerId, uint64_t banId);
std::shared_ptr<BanRecord> findBanByHwid(ServerId, std::string hwid);
std::shared_ptr<BanRecord> findBanByUid(ServerId, std::string uid);
std::shared_ptr<BanRecord> findBanByIp(ServerId, std::string ip);
std::shared_ptr<BanRecord> findBanByName(ServerId, std::string nickName);
std::shared_ptr<BanRecord> findBanExact(ServerId,
const std::string& /* reason */,
const std::string& /* uid */,
const std::string& /* ip */,
const std::string& /* display name */,
const std::string& /* hardware id */);
void deleteAllBans(ServerId sid);
void deleteAllBans(ServerId sid);
BanId registerBan(ServerId, ClientDbId invoker, std::string reason, std::string uid, std::string ip, std::string nickName, std::string hwid, std::chrono::time_point<std::chrono::system_clock> until);
void unban(ServerId, BanId);
void unban(std::shared_ptr<BanRecord>);
void updateBan(std::shared_ptr<BanRecord>);
BanId registerBan(ServerId, ClientDbId invoker, std::string reason, std::string uid, std::string ip, std::string nickName, std::string hwid, std::chrono::time_point<std::chrono::system_clock> until);
void unban(ServerId, BanId);
void unban(std::shared_ptr<BanRecord>);
void updateBan(std::shared_ptr<BanRecord>);
void updateBanReason(std::shared_ptr<BanRecord>, std::string);
void updateBanTimeout(std::shared_ptr<BanRecord>, std::chrono::time_point<std::chrono::system_clock>);
void updateBanReason(std::shared_ptr<BanRecord>, std::string);
void updateBanTimeout(std::shared_ptr<BanRecord>, std::chrono::time_point<std::chrono::system_clock>);
void trigger_ban(const std::shared_ptr<BanRecord>& /* record */,
ServerId /* server */,
const std::string& /* unique id */,
const std::string& /* hardware id */,
const std::string& /* nickname */,
const std::string& /* ip */
);
std::deque<std::shared_ptr<BanTrigger>> trigger_list(const std::shared_ptr<BanRecord>& /* record */, ServerId /* server id */, ssize_t /* offset */, ssize_t /* limit */);
void trigger_ban(const std::shared_ptr<BanRecord>& /* record */,
ServerId /* server */,
const std::string& /* unique id */,
const std::string& /* hardware id */,
const std::string& /* nickname */,
const std::string& /* ip */
);
std::deque<std::shared_ptr<BanTrigger>> trigger_list(const std::shared_ptr<BanRecord>& /* record */, ServerId /* server id */, ssize_t /* offset */, ssize_t /* limit */);
private:
sql::SqlManager* sql = nullptr;
std::atomic<BanId> current_ban_index;
};
}
}
private:
sql::SqlManager* sql = nullptr;
std::atomic<BanId> current_ban_index;
};
}
}

View File

@ -1,7 +1,7 @@
#pragma once
#include <string>
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "Definitions.h"
namespace ts {

View File

@ -7,7 +7,7 @@
#include <log/LogUtils.h>
#include <Properties.h>
#include <sql/mysql/MySQL.h>
#include <PermissionManager.h>
#include <PermissionRegister.h>
#include "SqlDataManager.h"
using namespace std;
@ -412,6 +412,7 @@ ROLLBACK;
CREATE_INDEX2R("conversation_blocks", "server_id", "conversation_id");
db_version(11);
default:
//CREATE TABLE `assigned_channel_groups` (`server_id` INT NOT NULL, `cldbid` INT NOT NULL, `channel_id` INT NOT NULL, `group_id` INT, `until` BIGINT DEFAULT -1, PRIMARY KEY `server_id`, `cldbid`, `channel_id`)
break;
}

View File

@ -15,7 +15,7 @@ using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
TokenManager::TokenManager(server::VirtualServer* handle) : handle(handle) {
@ -30,7 +30,7 @@ bool TokenManager::loadTokens() {
}
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
std::string token, description = "";
std::string token{}, description{};
auto type = static_cast<TokenType>(0xFF);
GroupId group = 0;
ChannelId chId = 0;

View File

@ -11,7 +11,7 @@ namespace ts {
class VirtualServer;
}
namespace token {
namespace server::tokens {
enum TokenType : uint8_t {
TOKEN_SERVER,
TOKEN_CHANNEL
@ -29,7 +29,7 @@ namespace ts {
class TokenManager {
public:
TokenManager(server::VirtualServer*);
explicit TokenManager(::ts::server::VirtualServer*);
~TokenManager();
bool loadTokens();
@ -39,9 +39,9 @@ namespace ts {
bool deleteToke(const std::string&);
private:
int loadTokenFromDb(int length, char** values, char** columns);
server::VirtualServer* handle;
ts::server::VirtualServer* handle;
std::vector<std::shared_ptr<TokenEntry>> tokens;
};
}
}
DEFINE_VARIABLE_TRANSFORM(ts::token::TokenType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast<ts::token::TokenType>(in.as<uint8_t>()));
DEFINE_VARIABLE_TRANSFORM(ts::server::tokens::TokenType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast<ts::server::tokens::TokenType>(in.as<uint8_t>()));

View File

@ -47,19 +47,19 @@ void MusicBotManager::cleanup_client_bots(ts::ClientDbId clientid) {
this->deleteBot(bot);
}
std::deque<std::shared_ptr<server::MusicClient>> MusicBotManager::available_bots() {
std::deque<std::shared_ptr<MusicClient>> MusicBotManager::available_bots() {
lock_guard lock(music_bots_lock);
return this->music_bots;
}
std::shared_ptr<server::MusicClient> MusicBotManager::find_bot_by_playlist(const std::shared_ptr<ts::music::PlayablePlaylist> &playlist) {
std::shared_ptr<MusicClient> MusicBotManager::find_bot_by_playlist(const std::shared_ptr<ts::music::PlayablePlaylist> &playlist) {
for(const auto& bot : this->available_bots())
if(bot->playlist() == playlist)
return bot;
return nullptr;
}
std::deque<std::shared_ptr<server::MusicClient>> MusicBotManager::listBots(ClientDbId clid) {
std::deque<std::shared_ptr<MusicClient>> MusicBotManager::listBots(ClientDbId clid) {
lock_guard lock(music_bots_lock);
std::deque<std::shared_ptr<server::MusicClient>> res;
for(const auto& bot : this->music_bots)
@ -67,7 +67,7 @@ std::deque<std::shared_ptr<server::MusicClient>> MusicBotManager::listBots(Clien
return res;
}
std::shared_ptr<server::MusicClient> MusicBotManager::createBot(ClientDbId owner) {
std::shared_ptr<MusicClient> MusicBotManager::createBot(ClientDbId owner) {
if(!config::license->isPremium()) {
if(this->current_bot_count() >= this->max_bots()) return nullptr; //Test the license
}
@ -177,7 +177,7 @@ int MusicBotManager::current_bot_count() {
return this->music_bots.size();
}
std::shared_ptr<server::MusicClient> MusicBotManager::findBotById(ClientDbId id) {
std::shared_ptr<MusicClient> MusicBotManager::findBotById(ClientDbId id) {
lock_guard lock(music_bots_lock);
for(const auto& bot : this->music_bots)
if(bot->getClientDatabaseId() == id) return bot;

View File

@ -13,7 +13,7 @@ using namespace ts::music;
using namespace std;
using namespace std::chrono;
Playlist::Playlist(const std::shared_ptr<ts::music::MusicBotManager> &manager, std::shared_ptr<ts::Properties> properties, std::shared_ptr<permission::v2::PermissionManager> permissions) :
Playlist::Playlist(const std::shared_ptr<ts::music::MusicBotManager> &manager, std::shared_ptr<ts::Properties> properties, std::shared_ptr<permission::v2::PermissionRegister> permissions) :
PlaylistPermissions{std::move(permissions)}, _properties{std::move(properties)}, manager{manager} { }
Playlist::~Playlist() {

View File

@ -90,7 +90,7 @@ namespace ts {
};
};
Playlist(const std::shared_ptr<MusicBotManager>& /* manager */, std::shared_ptr<Properties> /* properties */, std::shared_ptr<permission::v2::PermissionManager> /* permissions */);
Playlist(const std::shared_ptr<MusicBotManager>& /* manager */, std::shared_ptr<Properties> /* properties */, std::shared_ptr<permission::v2::PermissionRegister> /* permissions */);
virtual ~Playlist();
virtual void load_songs();

View File

@ -11,7 +11,7 @@ using namespace ts::music;
using namespace std;
using namespace std::chrono;
PlayablePlaylist::PlayablePlaylist(const std::shared_ptr<MusicBotManager> &handle, const std::shared_ptr<Properties> &props, const std::shared_ptr<permission::v2::PermissionManager>& perms) : Playlist(handle, props, perms) { }
PlayablePlaylist::PlayablePlaylist(const std::shared_ptr<MusicBotManager> &handle, const std::shared_ptr<Properties> &props, const std::shared_ptr<permission::v2::PermissionRegister>& perms) : Playlist(handle, props, perms) { }
PlayablePlaylist::~PlayablePlaylist() {}

View File

@ -24,7 +24,7 @@ namespace ts {
SHUFFLE
};
};
PlayablePlaylist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, const std::shared_ptr<permission::v2::PermissionManager>& /* permissions */);
PlayablePlaylist(const std::shared_ptr<MusicBotManager>& /* manager */, const std::shared_ptr<Properties>& /* properties */, const std::shared_ptr<permission::v2::PermissionRegister>& /* permissions */);
virtual ~PlayablePlaylist();
void load_songs() override;

View File

@ -9,7 +9,7 @@ using namespace ts;
using namespace ts::music;
PlaylistPermissions::PlaylistPermissions(std::shared_ptr<permission::v2::PermissionManager> permissions) : _permissions{std::move(permissions)} {}
PlaylistPermissions::PlaylistPermissions(std::shared_ptr<permission::v2::PermissionRegister> permissions) : _permissions{std::move(permissions)} {}
permission::v2::PermissionFlaggedValue PlaylistPermissions::calculate_client_specific_permissions(ts::permission::PermissionType permission, const std::shared_ptr<server::ConnectedClient>& client) {
auto val = this->_permissions->channel_permission(permission, client->getClientDatabaseId());

View File

@ -1,6 +1,6 @@
#pragma once
#include <PermissionManager.h>
#include <PermissionRegister.h>
namespace ts::server {
class VirtualServer;
@ -14,15 +14,15 @@ namespace ts::music {
ignore_playlist_owner,
do_no_require_granted
};
PlaylistPermissions(std::shared_ptr<permission::v2::PermissionManager> permissions);
PlaylistPermissions(std::shared_ptr<permission::v2::PermissionRegister> permissions);
inline const std::shared_ptr<permission::v2::PermissionManager>& permission_manager() const { return this->_permissions; }
inline const std::shared_ptr<permission::v2::PermissionRegister>& permission_manager() const { return this->_permissions; }
/* returns permission::ok if client has permissions */
permission::PermissionType client_has_permissions(const std::shared_ptr<server::ConnectedClient>& client, permission::PermissionType needed_permission, permission::PermissionType granted_permission, uint8_t /* ignore playlist owner */ = 0);
permission::v2::PermissionFlaggedValue calculate_client_specific_permissions(permission::PermissionType /* permission */, const std::shared_ptr<server::ConnectedClient>& /* client */);
protected:
const std::shared_ptr<permission::v2::PermissionManager> _permissions;
const std::shared_ptr<permission::v2::PermissionRegister> _permissions;
virtual bool is_playlist_owner(ClientDbId /* database id */) const = 0;
private:

View File

@ -9,7 +9,7 @@
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::server::server::udp;
//#define POW_DEBUG
//#define POW_ERROR
@ -33,7 +33,7 @@ void POWHandler::execute_tick() {
}), this->pending_clients.end());
}
void POWHandler::delete_client(const std::shared_ptr<ts::server::POWHandler::Client> &client) {
void POWHandler::delete_client(const std::shared_ptr<POWHandler::Client> &client) {
lock_guard lock(this->pending_clients_lock);
auto it = find(this->pending_clients.begin(), this->pending_clients.end(), client);
if(it != this->pending_clients.end())
@ -127,7 +127,7 @@ void POWHandler::handle_datagram(int socket, const sockaddr_storage &address,msg
}
}
void POWHandler::send_data(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
void POWHandler::send_data(const std::shared_ptr<POWHandler::Client> &client, const pipes::buffer_view &buffer) {
auto datagram = io::DatagramPacket::create(client->address, client->address_info, buffer.length() + MAC_SIZE + SERVER_HEADER_SIZE, nullptr);
if(!datagram) return; //Should never happen
@ -144,7 +144,7 @@ void POWHandler::send_data(const std::shared_ptr<ts::server::POWHandler::Client>
this->server->send_datagram(client->socket, datagram);
}
void POWHandler::reset_client(const std::shared_ptr<ts::server::POWHandler::Client> &client) {
void POWHandler::reset_client(const std::shared_ptr<POWHandler::Client> &client) {
uint8_t buffer[2] = {COMMAND_RESET, 0};
this->send_data(client, pipes::buffer_view{buffer, 2});
client->state = LowHandshakeState::COOKIE_GET;
@ -161,7 +161,7 @@ inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length)
*(--destination) = *(source++);
}
void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
void POWHandler::handle_cookie_get(const std::shared_ptr<POWHandler::Client> &client, const pipes::buffer_view &buffer) {
if(buffer.length() != 21) {
#ifdef POW_ERROR
debugMessage(this->get_server_id(), "[POW][{}][Cookie] Received an invalid packet with an invalid length. Expected {} bytes, but got {} bytes", net::to_string(client->address), 21, buffer.length());
@ -191,7 +191,7 @@ void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler:
client->state = LowHandshakeState::PUZZLE_GET;
}
void POWHandler::handle_puzzle_get(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
void POWHandler::handle_puzzle_get(const std::shared_ptr<POWHandler::Client> &client, const pipes::buffer_view &buffer) {
if(buffer.length() != 25) {
#ifdef POW_ERROR
debugMessage(this->get_server_id(), "[POW][{}][Puzzle] Received an invalid puzzle request with an invalid length. Expected {} bytes, but got {} bytes", net::to_string(client->address), 25, buffer.length());
@ -211,7 +211,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 */
{
@ -239,7 +239,7 @@ void POWHandler::handle_puzzle_get(const std::shared_ptr<ts::server::POWHandler:
client->state = LowHandshakeState::PUZZLE_SOLVE;
}
void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
void POWHandler::handle_puzzle_solve(const std::shared_ptr<POWHandler::Client> &client, const pipes::buffer_view &buffer) {
if(buffer.length() < 301) {
#ifdef POW_ERROR
debugMessage(this->get_server_id(), "[POW][{}][Puzzle] Received an invalid puzzle solution with an invalid length. Expected at least {} bytes, but got {} bytes", net::to_string(client->address), 301, buffer.length());
@ -283,7 +283,7 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandle
}
}
shared_ptr<VoiceClient> POWHandler::register_verified_client(const std::shared_ptr <ts::server::POWHandler::Client> &client) {
shared_ptr<ts::server::VoiceClient> POWHandler::register_verified_client(const std::shared_ptr <POWHandler::Client> &client) {
shared_ptr<VoiceClient> voice_client;
{
lock_guard lock(this->server->connectionLock);
@ -301,7 +301,7 @@ shared_ptr<VoiceClient> POWHandler::register_verified_client(const std::shared_p
voice_client->initialize();
voice_client->socket = client->socket;
voice_client->state = ConnectionState::INIT_LOW;
voice_client->state = ClientState::INITIALIZING;
memcpy(&voice_client->address_info, &client->address_info, sizeof(client->address_info));
{

View File

@ -3,12 +3,11 @@
#include <mutex>
#include <netinet/in.h>
#include <pipes/buffer.h>
#include <src/client/voice/PrecomputedPuzzles.h>
#include <Definitions.h>
#include "VoiceServer.h"
#include "src/VirtualServer.h"
#include "./udp-server/PrecomputedPuzzles.h"
namespace ts::server {
namespace ts::server::server::udp {
class POWHandler {
public:
enum LowHandshakeState : uint8_t {
@ -38,7 +37,7 @@ namespace ts::server {
uint32_t client_version;
std::shared_ptr<protocol::Puzzle> rsa_challenge;
std::shared_ptr<Puzzle> rsa_challenge;
};
explicit POWHandler(VoiceServer* /* server */);

View File

@ -13,6 +13,7 @@
#include <src/InstanceHandler.h>
#include <log/LogUtils.h>
#include <src/client/ConnectedClient.h>
#include "src/client/query/QueryClientConnection.h"
using namespace std;
using namespace std::chrono;
@ -23,6 +24,10 @@ using namespace ts::server;
#define TCP_NOPUSH TCP_CORK
#endif
namespace ts::server::server::query {
extern thread_local bool thread_is_event_loop;
}
QueryServer::QueryServer(sql::SqlManager* db) : sql(db) {
this->_executePool = new threads::ThreadPool(4, "EXEC Query");
}
@ -79,6 +84,7 @@ bool QueryServer::start(const deque<shared_ptr<QueryServer::Binding>> &bindings,
{
this->eventLoop = event_base_new();
this->ioThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [&]{
ts::server::server::query::thread_is_event_loop = true;
while(this->active) {
debugMessage(LOG_QUERY, "Entering event loop ({})", (void*) this->eventLoop);
event_base_loop(this->eventLoop, EVLOOP_NO_EXIT_ON_EMPTY);
@ -386,18 +392,19 @@ void QueryServer::on_client_receive(int _server_file_descriptor, short ev, void
}
}
shared_ptr<QueryClient> client = std::make_shared<QueryClient>(this, file_descriptor);
client->applySelfLock(client);
std::string error{};
auto client = std::make_shared<QueryClient>(this, file_descriptor);
if(!client->initialize(error, client)) {
logError(LOG_QUERY, "Failed to initialize newly accepted query client: {}", error);
return;
}
memcpy(&client->remote_address, &remote_address, sizeof(remote_address));
{
lock_guard lock(this->connected_clients_lock);
this->connectedClients.push_back(client);
}
client->preInitialize();
if(client->readEvent) {
event_add(client->readEvent, nullptr);
}
client->connection->add_read_event();
logMessage(LOG_QUERY, "Got new client from {}", client->getLoggingPeerIp() + ":" + to_string(client->getPeerPort()));
}

View File

@ -80,6 +80,8 @@ namespace ts {
bool change_query_password(const std::shared_ptr<QueryAccount>& /* account */, const std::string& /* new password */);
threads::ThreadPool* executePool() { return this->_executePool; }
[[nodiscard]] inline event_base* io_event_loop() { return this->eventLoop; }
[[nodiscard]] inline std::thread::id io_event_loop_id() { return {}; }
private:
sql::SqlManager* sql;

View File

@ -3,6 +3,7 @@
#include <deque>
#include <event.h>
#include <ThreadPool/Thread.h>
#include <ThreadPool/Mutex.h>
#include <condition_variable>
#include <pipes/buffer.h>
#include <misc/spin_lock.h>
@ -11,8 +12,11 @@ namespace ts {
namespace server {
class VirtualServer;
class VoiceServer;
class VoiceClient;
}
namespace connection {
class VoiceClientConnection;
}
namespace io {
union pktinfo_storage {
in_pktinfo v4;
@ -83,7 +87,7 @@ namespace ts {
datagram_packet_t dg_write_queue_head = nullptr;
datagram_packet_t dg_write_queue_tail = nullptr;
std::deque<std::weak_ptr<server::VoiceClient>> voice_write_queue;
std::deque<std::weak_ptr<connection::VoiceClientConnection>> voice_write_queue;
inline datagram_packet_t pop_dg_write_queue() {
std::lock_guard lock(this->write_queue_lock);
@ -111,13 +115,13 @@ namespace ts {
this->dg_write_queue_tail = packet;
}
inline void push_voice_write_queue(const std::shared_ptr<server::VoiceClient>& client) {
inline void push_voice_write_queue(const std::shared_ptr<connection::VoiceClientConnection>& client) {
std::lock_guard lock(this->write_queue_lock);
this->voice_write_queue.push_back(client);
}
/* return 0 on success | 1 on there is more, but success | 2 on empty */
inline int pop_voice_write_queue(std::shared_ptr<server::VoiceClient>& result) {
inline int pop_voice_write_queue(std::shared_ptr<connection::VoiceClientConnection>& result) {
std::lock_guard lock(this->write_queue_lock);
auto it_begin = this->voice_write_queue.begin();

View File

@ -5,6 +5,7 @@
#include <unistd.h>
#include <fcntl.h>
#include "VoiceServer.h"
#include "../client/voice/VoiceClientConnection.h"
#include "../client/voice/VoiceClient.h"
#include "../Configuration.h"
#include <log/LogUtils.h>
@ -28,7 +29,7 @@ extern InstanceHandler* serverInstance;
VoiceServer::VoiceServer(const std::shared_ptr<VirtualServer>& server) {
this->server = server;
this->pow_handler = make_unique<POWHandler>(this);
this->pow_handler = make_unique<server::udp::POWHandler>(this);
}
VoiceServer::~VoiceServer() { }
@ -109,7 +110,7 @@ bool VoiceServer::start(const std::deque<std::shared_ptr<VoiceServerBinding>>& b
return true;
}
void VoiceServer::triggerWrite(const std::shared_ptr<VoiceClient>& client) {
void VoiceServer::triggerWrite(const std::shared_ptr<connection::VoiceClientConnection>& client) {
if(!client) {
logError(this->server->getServerId(), "Invalid client for triggerWrite()");
return;
@ -118,7 +119,7 @@ void VoiceServer::triggerWrite(const std::shared_ptr<VoiceClient>& client) {
this->io->invoke_write(client);
}
void VoiceServer::schedule_command_handling(const ts::server::VoiceClient *client) {
void VoiceServer::schedule_command_handling(const connection::VoiceClientConnection *client) {
auto vmanager = serverInstance->getVoiceServerManager();
if(!vmanager)
return;
@ -129,7 +130,7 @@ void VoiceServer::schedule_command_handling(const ts::server::VoiceClient *clien
evloop->schedule(client->event_handle_packet);
}
void VoiceServer::tickHandshakingClients() {
void VoiceServer::tickClients() {
this->pow_handler->execute_tick();
decltype(this->activeConnections) connections;
@ -138,8 +139,7 @@ void VoiceServer::tickHandshakingClients() {
connections = this->activeConnections;
}
for(const auto& client : connections)
if(client->state == ConnectionState::INIT_HIGH || client->state == ConnectionState::INIT_LOW)
client->tick(system_clock::now());
client->tick();
}
void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) {
@ -157,7 +157,7 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
if (connection->acknowledge_handler.execute_resend(now, next, buffers, error) < 0) {
debugMessage(client->getServerId(), "{} Failed to execute packet resend: {}", CLIENT_STR_LOG_PREFIX_(client), error);
if(client->state == ConnectionState::CONNECTED) {
if(client->state == ClientState::CONNECTED) {
client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true);
} else {
client->close_connection(system_clock::now() + seconds(1));
@ -169,7 +169,7 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
}
//logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size());
buffers.clear();
connection->triggerWrite();
connection->register_client_for_write();
}
}
}
@ -342,7 +342,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
continue;
if(memcmp(&client->remote_address, &remote_address, sizeof(sockaddr_storage)) != 0) { /* verify the remote address */
if((read_buffer[12] & 0x80) == 0 && client->state == ConnectionState::CONNECTED) { /* only encrypted packets are allowed */
if((read_buffer[12] & 0x80) == 0 && client->state == ClientState::CONNECTED) { /* only encrypted packets are allowed */
if(client->connection->verify_encryption(read_buffer.view(0, bytes_read))) { /* the ip had changed */
auto old_address = net::to_string(client->remote_address);
auto new_address = net::to_string(remote_address);
@ -357,7 +357,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) {
}
}
if(client->state != ConnectionState::DISCONNECTED){
if(client->state != ClientState::DISCONNECTED){
client->connection->handle_incoming_datagram(read_buffer.view(0, bytes_read));
client = nullptr;
}
@ -447,7 +447,7 @@ void VoiceServer::handleMessageWrite(int fd, short events, void *_event_handle)
auto voice_server = event_handle->voice_server;
bool retrigger = false;
int buffer_state;
connection::WriteBufferStatus buffer_state{connection::WriteBufferStatus::UNSET};
IOData<0x100> io{};
io.file_descriptor = fd;
@ -472,15 +472,15 @@ void VoiceServer::handleMessageWrite(int fd, short events, void *_event_handle)
auto client_ptr = &*client;
TIMING_STEP(timings, "client get");
more_to_prepare = connection->preprocess_write_packets();
more_to_prepare = connection->encode_packets();
TIMING_STEP(timings, "client prepare");
while(system_clock::now() <= write_timeout) {
buffer_state = connection->pop_write_buffer(buffer);
more_to_write = buffer_state == 1;
more_to_write = buffer_state == connection::WriteBufferStatus::BUFFERS_LEFT;
TIMING_STEP(timings, "buffer pop");
if(buffer_state != 2) {
if(buffer_state != connection::WriteBufferStatus::NO_CHANGES) {
ssize_t res = write_datagram(io, client_ptr->remote_address, &client_ptr->address_info, buffer.length(), buffer.data_ptr());
TIMING_STEP(timings, "buffer write");
if(res != buffer.length()){

View File

@ -10,85 +10,78 @@
#include <protocol/ringbuffer.h>
#include "VoiceIOManager.h"
namespace ts {
namespace protocol {
class PuzzleManager;
}
namespace server {
class VirtualServer;
class ConnectedClient;
class VoiceClient;
namespace ts::connection {
class VoiceClientConnection;
}
namespace ts::server {
namespace server::udp {
class POWHandler;
struct VoiceServerBinding {
sockaddr_storage address{};
int file_descriptor = 0;
inline std::string address_string() { return net::to_string(address); }
inline uint16_t address_port() { return net::port(address); }
};
class VoiceServer {
friend class VoiceClient;
friend class io::VoiceIOManager;
friend struct io::IOEventLoopEvents;
friend class POWHandler;
public:
explicit VoiceServer(const std::shared_ptr<VirtualServer>& server);
~VoiceServer();
bool start(const std::deque<std::shared_ptr<VoiceServerBinding>>&, std::string&);
bool stop(const std::chrono::milliseconds& flushTimeout = std::chrono::milliseconds(1000));
std::shared_ptr<VoiceClient> findClient(ClientId);
std::shared_ptr<VoiceClient> findClient(sockaddr_in* addr, bool lock = true);
std::shared_ptr<VoiceClient> findClient(sockaddr_in6* addr, bool lock = true);
inline std::shared_ptr<VoiceClient> findClient(sockaddr_storage* address, bool lock = true) {
return address->ss_family == AF_INET ?
this->findClient((sockaddr_in*) address, lock) :
address->ss_family == AF_INET6 ?
this->findClient((sockaddr_in6*) address, lock) :
nullptr;
}
bool unregisterConnection(std::shared_ptr<VoiceClient>);
inline std::deque<std::shared_ptr<VoiceServerBinding>> activeBindings() {
std::deque<std::shared_ptr<VoiceServerBinding>> result;
for(const auto& entry : this->bindings)
if(entry->file_descriptor > 0) result.push_back(entry);
return result;
}
inline std::shared_ptr<VirtualServer> get_server() { return this->server; }
private:
std::unique_ptr<POWHandler> pow_handler;
std::shared_ptr<VirtualServer> server = nullptr;
bool running = false;
std::deque<std::shared_ptr<VoiceServerBinding>> bindings;
std::recursive_mutex connectionLock;
std::deque<std::shared_ptr<VoiceClient>> activeConnections;
public: //lib event
void triggerWrite(const std::shared_ptr<VoiceClient> &);
void schedule_command_handling(VoiceClient const *client);
void tickHandshakingClients();
void execute_resend(const std::chrono::system_clock::time_point& /* now */, std::chrono::system_clock::time_point& /* next resend */);
void send_datagram(int /* socket */, io::datagram_packet_t /* packet */);
std::shared_ptr<io::IOServerHandler> io;
private:
static void handleMessageRead(int, short, void *);
static void handleMessageWrite(int, short, void *);
/* execute loop */
/* TODO
std::mutex execute_list_lock;
protocol::RingBuffer<ClientId, 128, uint8_t> execute_list;
void run_execute_clients();
*/
};
}
class VirtualServer;
class ConnectedClient;
struct VoiceServerBinding {
sockaddr_storage address{};
int file_descriptor = 0;
inline std::string address_string() { return net::to_string(address); }
inline uint16_t address_port() { return net::port(address); }
};
class VoiceServer {
friend class VoiceClient;
friend class io::VoiceIOManager;
friend struct io::IOEventLoopEvents;
friend class server::udp::POWHandler;
public:
explicit VoiceServer(const std::shared_ptr<VirtualServer>& server);
~VoiceServer();
bool start(const std::deque<std::shared_ptr<VoiceServerBinding>>&, std::string&);
bool stop(const std::chrono::milliseconds& flushTimeout = std::chrono::milliseconds(1000));
std::shared_ptr<connection::VoiceClientConnection> findClient(sockaddr_in* addr, bool lock = true);
std::shared_ptr<connection::VoiceClientConnection> findClient(sockaddr_in6* addr, bool lock = true);
inline std::shared_ptr<connection::VoiceClientConnection> findClient(sockaddr_storage* address, bool lock = true) {
return address->ss_family == AF_INET ?
this->findClient((sockaddr_in*) address, lock) :
address->ss_family == AF_INET6 ?
this->findClient((sockaddr_in6*) address, lock) :
nullptr;
}
bool unregisterConnection(std::shared_ptr<connection::VoiceClientConnection>);
inline std::deque<std::shared_ptr<VoiceServerBinding>> activeBindings() {
std::deque<std::shared_ptr<VoiceServerBinding>> result;
for(const auto& entry : this->bindings)
if(entry->file_descriptor > 0) result.push_back(entry);
return result;
}
inline std::shared_ptr<VirtualServer> get_server() { return this->server; }
private:
std::unique_ptr<server::udp::POWHandler> pow_handler;
std::shared_ptr<VirtualServer> server = nullptr;
bool running = false;
std::deque<std::shared_ptr<VoiceServerBinding>> bindings;
std::recursive_mutex connectionLock;
std::deque<std::shared_ptr<connection::VoiceClientConnection>> activeConnections;
public: //lib event
void triggerWrite(const std::shared_ptr<connection::VoiceClientConnection> &);
void schedule_command_handling(connection::VoiceClientConnection const *client);
void tickClients();
void execute_resend(const std::chrono::system_clock::time_point& /* now */, std::chrono::system_clock::time_point& /* next resend */);
void send_datagram(int /* socket */, io::datagram_packet_t /* packet */);
std::shared_ptr<io::IOServerHandler> io;
private:
static void handleMessageRead(int, short, void *);
static void handleMessageWrite(int, short, void *);
};
}

View File

@ -1,50 +1,50 @@
#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::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()};
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 +66,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 +94,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;
}));

View File

@ -0,0 +1,39 @@
#pragma once
#include <tommath.h>
#include <memory>
#include <vector>
#include <misc/spin_lock.h>
#include <random>
namespace ts::server::server::udp {
struct Puzzle {
mp_int x;
mp_int n;
int level;
mp_int result;
uint8_t data_x[64];
uint8_t data_n[64];
uint8_t data_result[64];
};
class PuzzleManager {
public:
PuzzleManager();
~PuzzleManager();
[[nodiscard]] bool precompute_puzzles(size_t amount);
[[nodiscard]] size_t precomputed_puzzle_count();
[[nodiscard]] std::shared_ptr<Puzzle> next_puzzle();
private:
void generate_puzzle(std::mt19937&);
size_t cache_index{0};
spin_lock cache_lock{};
std::vector<std::shared_ptr<Puzzle>> cached_puzzles{};
};
}

View File

@ -0,0 +1,5 @@
//
// Created by WolverinDEV on 07/03/2020.
//
#include "UDPServer.h"

View File

@ -0,0 +1,125 @@
#pragma once
#include <thread>
#include <event.h>
#include <vector>
#include <misc/spin_lock.h>
#include <Definitions.h>
#include <mutex>
#include <deque>
namespace ts::server {
class VirtualServer;
}
namespace ts::connection {
class VoiceClientConnection;
}
namespace ts::server::vserver {
class VirtualServerBase;
}
namespace ts::server::server::udp {
struct datagram_packet {
union pktinfo_storage {
in_pktinfo v4;
in6_pktinfo v6;
};
datagram_packet* next_packet;
sockaddr_storage address;
pktinfo_storage address_info;
size_t data_length;
uint8_t data[0];
};
static_assert(std::is_trivially_destructible<datagram_packet>::value);
static_assert(std::is_trivially_constructible<datagram_packet>::value);
template <typename T, size_t N>
struct write_ring_queue {
std::array<T, N> memory{};
size_t current_index{0};
size_t filled_index{0};
[[nodiscard]] constexpr inline auto max_size() const { return N; }
[[nodiscard]] inline size_t current_size() const { return this->filled_index - this->current_index; }
[[nodiscard]] inline bool pop_entry(T& result) {
if(this->current_index >= this->filled_index) return false;
return this->memory[this->current_index++ % N];
}
[[nodiscard]] inline bool push_entry(T&& entry) {
if(this->filled_index - this->current_index >= N) return false;
this->memory[this->filled_index++ % N] = std::forward(entry);
return true;
}
};
struct io_loop;
struct io_loop_entry {
io_loop* io_loop{nullptr};
int file_descriptor{0};
struct event* event_read{};
struct event* event_write{};
spin_lock write_queue_lock{};
datagram_packet* dg_write_queue_head{nullptr};
datagram_packet* dg_write_queue_tail{nullptr};
write_ring_queue<std::weak_ptr<connection::VoiceClientConnection>, 1024 * 8> voice_write_queue{};
};
struct io_loop {
std::thread base_dispatcher{};
struct event_base* event_base{nullptr};
std::mutex entries_mutex{};
std::vector<io_loop_entry*> registered_entries{};
};
struct io_binding {
VirtualServer* virtual_server{nullptr};
sockaddr_storage address{};
size_t loop_entry_index{0};
std::vector<io_loop_entry*> loop_entries{};
};
enum struct ServerRegisterResult {
SUCCESS,
FAILED_TO_BIND
};
class Server {
public:
Server();
~Server();
bool initialize(std::string& /* error */);
void finalize();
ServerRegisterResult register_virtual_server(VirtualServer* /* server */);
/* this will block until all executions have been finished */
void unregister_virtual_server(VirtualServer* /* server */);
void schedule_client_write(const std::shared_ptr<connection::VoiceClientConnection>& /* client */);
void unregister_client(const std::shared_ptr<connection::VoiceClientConnection>& /* client */);
private:
std::mutex io_lock{};
std::vector<io_loop*> io_loops{};
std::mutex bindings_lock{};
std::vector<io_binding*> io_bindings{}; /* may contains nullptr! */
std::thread client_tick_thread{};
void execute_client_ticking();
};
}

View File

@ -0,0 +1,385 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include <deque>
#include "./ClientChannelService.h"
#include "../client/ConnectedClient.h"
#include "../vserver/VirtualServer.h"
#include "../groups/GroupManager.h"
#include "../groups/GroupAssignmentManager.h"
#include <misc/timer.h>
#include <misc/sassert.h>
#include <log/LogUtils.h>
using namespace ts::server::channels;
ts::ServerId ClientChannelService::get_server_id() const {
return this->virtual_server_->server_id();
}
ChannelGroupInheritance ClientChannelService::calculate_channel_group(ChannelId channel_id, ClientDbId client_dbid, ClientType client_type) {
auto server_state_lock = this->virtual_server_->lock_server_status();
auto tree_lock = this->virtual_server_->lock_channel_clients();
tree_lock.auto_lock_shared();
auto assignments = this->virtual_server_->group_manager()->assignments().channel_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, client_dbid);
auto inheritance_channel = this->virtual_server_->server_channel_tree()->findChannel(channel_id);
while(inheritance_channel) {
auto inheritance_channel_id = inheritance_channel->channelId();
auto assignment = std::find_if(assignments.begin(), assignments.end(), [&](const groups::ChannelGroupAssignment& assignment) {
return assignment.channel_id == inheritance_channel_id;
});
if(assignment == assignments.end() || !assignment->group_id) {
auto permission = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end);
if(permission::v2::permission_granted(1, permission))
break;
inheritance_channel = inheritance_channel->parent();
} else {
return {
assignment->group_id,
inheritance_channel_id
};
}
}
auto default_group = this->virtual_server_->group_manager()->default_channel_group(client_type);
assert(default_group);
return {
default_group->group_id(),
channel_id
};
}
/*
* General notice:
* We're locking the clients command_lock, so when changing the currentChannel variable nothing gets messed up.
* As a little note: This must be done before locking the channel tree at all.
*/
ClientMoveResult ClientChannelService::client_ban(const std::shared_ptr<ConnectedClient> &target,
const std::shared_ptr<ConnectedClient> &invoker, const std::string &reason, size_t time,
ts::rwshared_lock<ts::rw_mutex> &tree_lock) {
tree_lock.auto_unlock();
auto command_lock = target->lock_command_handling();
if(auto err = tree_lock.auto_lock_exclusive(); err)
return ClientMoveResult::TREE_LOCK_FAILED;
auto server_state_lock = this->virtual_server_->lock_server_status();
std::unique_lock client_chan_tree_lock{target->get_channel_lock()};
auto old_channel = target->getChannel();
target->setChannel(nullptr);
client_chan_tree_lock.unlock();
ChannelId old_channel_id{0};
if(old_channel) {
old_channel_id = old_channel->channelId();
for(const auto& client : this->virtual_server_->connected_clients(false)) {
if(!client || client == target)
continue;
std::unique_lock client_channel_lock(client->get_channel_lock());
if(client->isClientVisible(target, false))
client->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(old_channel);
assert(s_channel);
s_channel->unregister_client(target);
}
target->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
return ClientMoveResult::SUCCESS;
}
ClientMoveResult ClientChannelService::client_kick(const std::shared_ptr<ConnectedClient> &target,
const std::shared_ptr<ConnectedClient> &invoker, const std::string &reason,
const std::shared_ptr<ServerChannel> &target_channel,
ts::rwshared_lock<ts::rw_mutex> &tree_lock) {
if(target_channel) {
return this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
} else {
tree_lock.auto_unlock();
auto command_lock = target->lock_command_handling();
if(auto err = tree_lock.auto_lock_exclusive(); err)
return ClientMoveResult::TREE_LOCK_FAILED;
auto server_state_lock = this->virtual_server_->lock_server_status();
std::unique_lock client_chan_tree_lock{target->get_channel_lock()};
auto old_channel = target->getChannel();
target->setChannel(nullptr);
client_chan_tree_lock.unlock();
ChannelId old_channel_id{0};
if(old_channel) {
old_channel_id = old_channel->channelId();
for(const auto& client : this->virtual_server_->connected_clients(false)) {
if(!client || client == target)
continue;
std::unique_lock client_channel_lock(client->get_channel_lock());
if(client->isClientVisible(target, false))
client->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(old_channel);
assert(s_channel);
s_channel->unregister_client(target);
}
target->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
}
return ClientMoveResult::SUCCESS;
}
ClientMoveResult ClientChannelService::client_move(const std::shared_ptr<ConnectedClient> &target,
std::shared_ptr<ServerChannel> s_target_channel,
const std::shared_ptr<ConnectedClient> &invoker,
const std::string &reason_message,
ViewReasonId reason_id,
bool notify_client,
ts::rwshared_lock<ts::rw_mutex> &tree_lock) {
TIMING_START(timings);
tree_lock.auto_unlock();
auto command_lock = target->lock_command_handling();
if(auto err = tree_lock.auto_lock_exclusive(); err)
return ClientMoveResult::TREE_LOCK_FAILED;
auto server_state_lock = this->virtual_server_->lock_server_status();
TIMING_STEP(timings, "tree lock setup");
if(target->getChannel() == s_target_channel)
return ClientMoveResult::SUCCESS;
/* first step: resolve the target channel / or fix missing */
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->getChannel());
assert(!target->getChannel() || s_source_channel != nullptr);
auto server_channel_tree = this->virtual_server_->server_channel_tree();
std::deque<property::ClientProperties> client_updates;
std::deque<property::ClientProperties> changed_groups{};
if(s_target_channel) {
/* don't let the client join a deleted channel */
if(s_target_channel->deleted) {
s_target_channel = dynamic_pointer_cast<ServerChannel>(server_channel_tree->getDefaultChannel());
assert(s_target_channel);
}
/* update the group properties here already, so for all enter views we could just send the new props directly */
changed_groups = this->virtual_server_->group_manager()->assignments().update_client_group_properties(target, s_target_channel->channelId());
client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter?
}
auto l_target_channel = s_target_channel ? server_channel_tree->findLinkedChannel(s_target_channel->channelId()) : nullptr;
auto l_source_channel = s_source_channel ? server_channel_tree->findLinkedChannel(s_source_channel->channelId()) : nullptr;
TIMING_STEP(timings, "channel lookup ");
/* second step: show the target channel to the client if its not shown and let him subscibe to the channel */
if(s_target_channel && notify_client) {
std::unique_lock client_channel_lock(target->get_channel_lock());
bool success = false;
/* TODO: Use a bunk here and not a notify for every single */
for(const auto& channel : target->channel_view()->show_channel(l_target_channel, success))
target->notifyChannelShow(channel->channel(), channel->previous_channel);
sassert(success);
if(!success)
return ClientMoveResult::VIEW_INSERT_FAILED;
target->subscribeChannel({s_target_channel}, false, true);
}
TIMING_STEP(timings, "target show chan");
if(s_target_channel) {
for(const auto& client : this->virtual_server_->connected_clients(false)) {
if (!notify_client && client == target) continue;
std::unique_lock client_channel_lock{client->get_channel_lock()};
auto chan_target = client->channel_view()->find_channel(s_target_channel);
if(chan_target) {
auto chan_source = client->channel_view()->find_channel(s_source_channel);
if(chan_source) {
if (chan_target->subscribed || client == target) {
if (client == target || client->isClientVisible(target, false)) {
client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false);
} else {
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
}
} else if(client->isClientVisible(target, false)){
//Client got out of view
client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? std::string{"view left"} : reason_message, invoker, false);
}
} else {
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->get_server_id(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client));
//Client entered view
if(chan_target->subscribed)
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
}
} else {
/* target channel isn't visible => so client gone out of view */
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->get_server_id(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client));
//Test for in view? (Notify already does but nvm)
if(client->isClientVisible(target, false)){
//Client got out of view
if(reason_id == ViewReasonId::VREASON_USER_ACTION)
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
else
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
}
}
}
if(s_source_channel)
s_source_channel->unregister_client(target);
s_target_channel->register_client(target);
} else {
/* client left the server */
if(target->getChannel()) {
for(const auto& client : this->virtual_server_->connected_clients(false)) {
if(!client || client == target)
continue;
std::unique_lock client_channel_lock(client->get_channel_lock());
if(client->isClientVisible(target, false))
client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false);
}
s_source_channel->unregister_client(target);
}
}
TIMING_STEP(timings, "notify viewers ");
target->setChannel(s_target_channel);
tree_lock.downgrade_lock();
std::unique_lock client_channel_lock(target->get_channel_lock());
TIMING_STEP(timings, "lock own ch tree");
if (s_source_channel) {
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->virtual_server_->group_manager()->assignments().cleanup_channel_temporary_assignment(target->getClientDatabaseId(), s_source_channel->channelId());
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
if(update) {
target->properties()[property::CLIENT_IS_TALKER] = 0;
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
target->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
client_updates.push_back(property::CLIENT_IS_TALKER);
client_updates.push_back(property::CLIENT_TALK_REQUEST);
client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG);
}
TIMING_STEP(timings, "src chan up");
}
if (s_target_channel) {
if(target->update_cached_permissions()) /* update cached calculated permissions */
target->sendNeededPermissions(false);
TIMING_STEP(timings, "perm gr upd");
if(s_source_channel) {
std::deque<ChannelId> deleted;
for(const auto& channel : target->channel_view()->test_channel(l_source_channel, l_target_channel))
deleted.push_back(channel->channelId());
if(!deleted.empty())
target->notifyChannelHide(deleted, false);
/* force unsubscribe from old channel */
auto i_source_channel = s_source_channel->channelId();
if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) {
auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) {
auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) {
logTrace(this->get_server_id(), "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)",
CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(),
i_source_channel
);
target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions)
}
}
}
TIMING_STEP(timings, "src hide ts");
}
}
client_channel_lock.unlock();
/* both methods lock if they require stuff */
this->virtual_server_->broadcast_service().client_updated(target, client_updates, s_source_channel ? true : false);
TIMING_STEP(timings, "notify cpro");
if(s_target_channel) {
target->updateChannelClientProperties(false, s_source_channel ? true : false);
TIMING_STEP(timings, "notify_t_pr");
}
debugMessage(this->get_server_id(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings));
return ClientMoveResult ::SUCCESS;
}
/*
* 1. flag channel as deleted (lock channel tree so no moves)
* 2. Gather all clients within the channel (lock their execute lock)
* 3. Unlock channel tree and lock client locks
* 4. lock channel tree again and move the clients (No new clients should be joined because channel is flagged as deleted!)
*
* Note: channel cant be a ref because the channel itself gets deleted!
*/
ChannelDeleteResult ClientChannelService::delete_channel(std::shared_ptr<ServerChannel> channel, const std::shared_ptr<ConnectedClient> &invoker, const std::string &kick_message, ts::rwshared_lock<ts::rw_mutex> &tree_lock) {
if(auto err = tree_lock.auto_lock_exclusive(); err)
return ChannelDeleteResult::TREE_LOCK_FAILED;
if(channel->deleted)
return ChannelDeleteResult::SUCCESS;
std::deque<std::shared_ptr<ConnectedClient>> clients;
{
for(const auto& sub_channel : this->virtual_server_->server_channel_tree()->channels(channel)) {
auto s_channel = dynamic_pointer_cast<ServerChannel>(sub_channel);
assert(s_channel);
auto chan_clients = this->virtual_server_->find_clients_by_channel(s_channel);
clients.insert(clients.end(), chan_clients.begin(), chan_clients.end());
s_channel->deleted = true;
}
auto chan_clients = this->virtual_server_->find_clients_by_channel(channel);
clients.insert(clients.end(), chan_clients.begin(), chan_clients.end());
channel->deleted = true;
}
auto default_channel = dynamic_pointer_cast<ServerChannel>(this->virtual_server_->server_channel_tree()->getDefaultChannel());
assert(default_channel);
tree_lock.auto_unlock();
std::deque<std::unique_lock<threads::Mutex>> command_locks;
for(const auto& client : clients)
command_locks.push_back(std::move(client->lock_command_handling<std::unique_lock<threads::Mutex>>()));
for(const auto& client : clients) {
auto result = this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
if(result != ClientMoveResult::SUCCESS)
return ChannelDeleteResult::CLIENT_MOVE_FAILED;
}
if(auto err = tree_lock.auto_lock_exclusive(); err)
return ChannelDeleteResult::TREE_LOCK_FAILED;
command_locks.clear();
auto channel_ids = this->virtual_server_->server_channel_tree()->delete_channel_root(channel);
tree_lock.auto_lock_shared();
for(auto& client : this->virtual_server_->connected_clients(false)) {
std::unique_lock client_channel_lock(client->get_channel_lock());
client->notifyChannelDeleted(client->channel_view()->delete_channel_root(channel), invoker);
}
return ChannelDeleteResult::SUCCESS;
}

View File

@ -0,0 +1,94 @@
#pragma once
#include <string>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <Definitions.h>
#include <lock/rw_mutex.h>
namespace ts {
class ServerChannel;
}
namespace ts::server {
class ConnectedClient;
namespace vserver {
class VirtualServerBase;
}
}
namespace ts::server::channels {
enum struct ClientMoveResult {
SUCCESS,
TREE_LOCK_FAILED,
VIEW_INSERT_FAILED
};
enum struct ChannelDeleteResult {
SUCCESS,
TREE_LOCK_FAILED,
CLIENT_MOVE_FAILED
};
struct ChannelGroupInheritance {
GroupId group_id{};
ChannelId inherited_channel_id{};
};
class ClientChannelService {
public:
explicit ClientChannelService(vserver::VirtualServerBase*);
~ClientChannelService();
bool initialize(std::string& /* error */);
/* channel group related stuff */
/* this will lock the channel tree in read mode */
[[nodiscard]] ChannelGroupInheritance calculate_channel_group(ChannelId /* channel id */, ClientDbId /* client db id */, ClientType /* fallback type */);
/*
* ATTENTION: The methods client_ban, client_kick, client_move unlock the channel tree completely.
* All channel tree related variable must be checked again before using.
*/
/* Note: Use only this method to disconnect the client and notify everybody else that he has been banned! */
/* remove a client from the channel tree because he's banned */
[[nodiscard]] ClientMoveResult client_ban(
const std::shared_ptr<ConnectedClient>& /* client */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* reason */,
size_t /* length */,
ts::rwshared_lock<ts::rw_mutex>& /* tree lock */);
/* remove/move a client from/within the channel tree because he has been kicked */
[[nodiscard]] ClientMoveResult client_kick(
const std::shared_ptr<ConnectedClient>& /* client */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* reason */,
const std::shared_ptr<ServerChannel>& /* target channel */,
ts::rwshared_lock<ts::rw_mutex>& /* tree lock */
);
/* move a client within the channel tree */
[[nodiscard]] ClientMoveResult client_move(
const std::shared_ptr<ConnectedClient>& /* client */,
std::shared_ptr<ServerChannel> /* target channel */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* reason */,
ViewReasonId /* reason id */,
bool /* notify the client */,
ts::rwshared_lock<ts::rw_mutex>& /* tree lock */
);
[[nodiscard]] ChannelDeleteResult delete_channel(
std::shared_ptr<ServerChannel> /* target channel */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* kick message */,
ts::rwshared_lock<ts::rw_mutex>& /* tree lock */
);
private:
vserver::VirtualServerBase* virtual_server_{nullptr};
[[nodiscard]] ServerId get_server_id() const;
};
}

View File

@ -0,0 +1,372 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include <log/LogUtils.h>
#include "./PermissionsService.h"
#include "../InstanceHandler.h"
#include "../vserver/VirtualServerBase.h"
#include "../vserver/VirtualServer.h"
#include "../groups/Group.h"
#include "../groups/GroupManager.h"
#include "../groups/GroupAssignmentManager.h"
#include "../client/ConnectedClient.h"
using namespace ts::permission;
using namespace ts::permission::v2;
using namespace ts::server::permissions;
PermissionService::PermissionService(ts::server::vserver::VirtualServerBase *handle) : virtual_server_{handle} {}
bool PermissionService::initialize(std::string &) { return true; }
bool PermissionService::load_data(std::string &) { return true; }
void PermissionService::unload_data() {}
ts::ServerId PermissionService::get_server_id() const {
return this->virtual_server_->server_id();
}
PermissionFlaggedValue PermissionService::calculate_client_permission(
PermissionType permission,
ClientDbId cldbid,
ClientType type,
ChannelId channel,
bool granted,
std::shared_ptr<CalculateCache> cache) {
auto result = this->calculate_client_permissions({permission}, cldbid, type, channel, granted, std::move(cache));
if(result.empty()) return {0, false};
return result.front().second;
}
std::vector<std::pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlaggedValue>> PermissionService::calculate_client_permissions(
const std::deque<permission::PermissionType>& permissions,
ClientDbId client_dbid,
ClientType client_type,
ChannelId channel_id,
bool calculate_granted,
std::shared_ptr<CalculateCache> cache) {
if(permissions.empty()) return {};
std::vector<std::pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlaggedValue>> result{};
result.reserve(permissions.size());
if(cache->client_database_id && cache->client_database_id != client_dbid)
cache = nullptr;
if(!cache)
cache = std::make_shared<CalculateCache>();
cache->client_type = client_type;
cache->client_database_id = client_dbid;
if(!cache->client_permissions)
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->get_server_id(), client_dbid);
bool have_skip_permission = false;
int skip_permission_type = -1; /* -1 := unset | 0 := skip, not explicit | 1 := skip, explicit */
bool have_skip;
/*
* server_group_data[0] := Server group id
* server_group_data[1] := Skip flag
* server_group_data[2] := Negate flag
* server_group_data[3] := Permission value
*/
typedef std::tuple<GroupId, bool, bool, permission::PermissionValue> GroupData;
bool server_group_data_initialized = false;
std::vector<GroupData> server_group_data;
GroupData* active_server_group;
/* function to calculate skip permission */
auto calculate_skip = [&]{
skip_permission_type = 0;
/* test for skip permission within the client permission manager */
{
auto skip_value = cache->client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(skip_value.has_value) {
have_skip_permission = skip_value.value == 1;
skip_permission_type = 1;
logTrace(this->get_server_id(), "[Permission] Found skip permission in client permissions. Value: {}", have_skip_permission);
}
}
/* test for skip permission within all server groups */
if(skip_permission_type != 1) {
this->load_group_assignments(cache);
assert(cache->assignment_server_groups_set);
for(const auto& assignment : cache->assignment_server_groups) {
auto group_permissions = assignment->permissions();
auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(flagged_value.has_value) {
have_skip_permission |= flagged_value.value == 1;
if(have_skip_permission) {
logTrace(this->get_server_id(), "[Permission] Found skip permission in client server group. Group: {} ({}), Value: {}", assignment->group_id(), assignment->display_name(), have_skip_permission);
break;
}
}
}
}
};
auto initialize_group_data = [&](const permission::PermissionType& permission_type) {
server_group_data_initialized = true;
active_server_group = nullptr;
this->load_group_assignments(cache);
assert(cache->assignment_server_groups_set);
server_group_data.resize(cache->assignment_server_groups.size());
auto it = server_group_data.begin();
for(auto& group : cache->assignment_server_groups) {
auto group_permissions = group->permissions();
auto permission_flags = group_permissions->permission_flags(permission_type);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(!flag_set)
continue;
//TODO: Test if there is may a group channel permissions
auto value = group_permissions->permission_values(permission_type);
*it = std::make_tuple(group->group_id(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value);
it++;
}
if(it == server_group_data.begin())
return; /* no server group has that permission */
server_group_data.erase(it, server_group_data.end()); /* remove unneeded */
auto found_negate = false;
for(auto& group : server_group_data) {
if(std::get<2>(group)) {
found_negate = true;
break;
}
}
if(found_negate) {
server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end());
logTrace(this->get_server_id(), "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size());
if(server_group_data.empty())
logTrace(this->get_server_id(), "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, client_dbid);
permission::PermissionValue current_lowest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) {
current_lowest = std::get<3>(group);
active_server_group = &group;
}
}
} else {
permission::PermissionValue current_highest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) {
current_highest = std::get<3>(group);
active_server_group = &group;
}
}
}
};
for(const auto& permission : permissions) {
if(permission == permission::b_client_skip_channelgroup_permissions) {
if(skip_permission_type == -1) /* initialize skip flag */
calculate_skip();
result.push_back({permission, {have_skip_permission, skip_permission_type == 1}});
continue;
}
server_group_data_initialized = false; /* reset all group data */
auto client_permission_flags = cache->client_permissions->permission_flags(permission);
/* lets try to resolve the channel specific permission */
if(channel_id > 0 && client_permission_flags.channel_specific) {
auto data = cache->client_permissions->channel_permission(permission, channel_id);
if(calculate_granted ? data.flags.grant_set : data.flags.value_set) {
result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}});
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.values.value);
continue;
}
}
have_skip = channel_id == 0;
if(!have_skip) {
/* look if somewhere is the skip permission flag set */
if(skip_permission_type == -1) /* initialize skip flag */
calculate_skip();
have_skip = have_skip_permission;
}
if(!have_skip) {
/* okey we've no global skip. Then now lookup the groups and the client permissions */
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
/* okey the client has the permission, this counts */
have_skip = client_permission_flags.skip;
} else {
if(!server_group_data_initialized)
initialize_group_data(permission);
if(active_server_group)
have_skip = std::get<1>(*active_server_group);
}
}
if(!have_skip) {
/* lookup the channel group */
{
this->load_channel_assignment(cache, channel_id);
assert(cache->assignment_channel_group_channel == channel_id);
auto channel_assignment = cache->assignment_channel_group;
if(channel_assignment) {
auto group_permissions = channel_assignment->permissions();
auto permission_flags = group_permissions->permission_flags(permission);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(flag_set) {
auto value = group_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? value.grant : value.value, true}});
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", client_dbid, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value);
continue;
}
}
}
/* lookup the channel permissions. Whyever? */
{
this->load_server_channel(cache, channel_id);
if(cache->server_channel) {
auto channel_permissions = cache->server_channel->permissions();
auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission);
if(data.has_value) {
result.push_back({permission, {data.value, true}});
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.value);
continue;
}
}
}
}
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
auto client_value = cache->client_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}});
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", client_dbid, permission::resolvePermissionData(permission)->name, client_value.value);
continue;
}
if(!server_group_data_initialized)
initialize_group_data(permission);
if(active_server_group) {
result.push_back({permission, {get<3>(*active_server_group), true}});
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", client_dbid, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group));
continue;
}
logTrace(this->get_server_id(), "[Permission] Calculation for client {} of permission {} returned in no permission.", client_dbid, permission::resolvePermissionData(permission)->name);
result.push_back({permission, {permNotGranted, false}});
}
return result;
}
void PermissionService::load_group_assignments(const std::shared_ptr<CalculateCache> &cache) {
if(cache->assignment_server_groups_set) return;
cache->assignment_server_groups_set = true;
auto group_manager = this->virtual_server_->group_manager();
auto assignments = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, cache->client_database_id);
cache->assignment_server_groups.reserve(assignments.size());
for(auto& entry : assignments) {
auto group = group_manager->find_server_group(groups::GroupCalculateMode::GLOBAL, entry);
if(!group) continue;
cache->assignment_server_groups.push_back(group);
}
if(cache->assignment_server_groups.empty())
cache->assignment_server_groups.push_back(group_manager->default_server_group(cache->client_type));
}
void PermissionService::load_channel_assignment(const std::shared_ptr<CalculateCache> &cache, ts::ChannelId channel_id) {
if(cache->assignment_channel_group_channel == channel_id) return;
cache->assignment_channel_group_channel = channel_id;
auto group_manager = this->virtual_server_->group_manager();
auto channel_groups = group_manager->assignments().channel_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, cache->client_database_id);
auto it = std::find_if(channel_groups.begin(), channel_groups.end(), [&](const groups::ChannelGroupAssignment& assignment) {
return assignment.channel_id == channel_id;
});
GroupId group_id{0};
if(it != channel_groups.end())
group_id = it->group_id;
if(!group_id) {
auto assignment = this->virtual_server_->channel_service().calculate_channel_group(channel_id, cache->client_database_id, cache->client_type);
group_id = assignment.group_id;
}
cache->assignment_channel_group = group_manager->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!cache->assignment_channel_group)
cache->assignment_channel_group = group_manager->default_channel_group(cache->client_type);
}
void PermissionService::load_server_channel(const std::shared_ptr<CalculateCache> &cache, ts::ChannelId channel_id) {
if(cache->last_server_channel == channel_id) return;
cache->last_server_channel = channel_id;
auto channel_tree = this->virtual_server_->server_channel_tree();
auto tree_lock = this->virtual_server_->lock_channel_clients();
cache->server_channel = channel_tree->findChannel(channel_id);
}
PermissionResetResult PermissionService::reset_server_permissions() {
//TODO: Delete all tokens?
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute());
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute());
LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute());
auto group_manager = this->virtual_server_->group_manager();
group_manager->reset_groups(false);
auto properties = this->virtual_server_->server_properties();
properties->find(property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY) = "";
properties->find(property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY) = true;
if(auto vs = dynamic_cast<vserver::VirtualServer*>(this->virtual_server_); vs){
auto default_admin_group_id = serverInstance->properties().find(property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP).as_save<GroupId>();
auto group = this->virtual_server_->group_manager()->find_server_group(groups::GroupCalculateMode::GLOBAL, default_admin_group_id);
if(!group) {
logCritical(LOG_INSTANCE, "Missing default template server admin group ({}). We're not generating an admin token.", default_admin_group_id);
} else {
auto group_name = group->display_name();
group = this->virtual_server_->group_manager()->find_server_group_by_name(groups::GroupCalculateMode::LOCAL, group_name);
if(!group) {
logError(this->get_server_id(), "Could not find server admin group from template name ({}). We're not generating an admin token.", group_name, group);
} else {
auto token = vs->token_manager().createToken(ts::server::tokens::TOKEN_SERVER, group->group_id(), "Default server token for the server admin.");
if(!token) {
logError(this->get_server_id(), "Failed to generate default server admin token.");
} else {
properties->find(property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY) = token->token;
properties->find(property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY) = true;
}
}
}
}
for(const auto& client : this->virtual_server_->connected_clients(false)) {
if(client->getType() != ClientType::CLIENT_QUERY) {
client->notifyServerGroupList();
client->notifyChannelGroupList();
}
if(this->virtual_server_->broadcast_service().client_updated(client, group_manager->assignments().update_client_group_properties(client, client->getChannelId()))) {
if(client->update_cached_permissions()) /* update cached calculated permissions */
client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
}
client->updateChannelClientProperties(true, true);
}
return PermissionResetResult::SUCCESS;
}

View File

@ -0,0 +1,74 @@
#pragma once
#include <string>
#include <PermissionRegister.h>
namespace ts::server {
namespace groups {
class ServerGroup;
class ChannelGroup;
}
namespace vserver {
class VirtualServerBase;
}
}
namespace ts::server::permissions {
struct CalculateCache {
ClientDbId client_database_id{0};
ClientType client_type{ClientType::UNKNOWN};
std::shared_ptr<permission::v2::PermissionRegister> client_permissions;
std::vector<std::shared_ptr<groups::ServerGroup>> assignment_server_groups;
bool assignment_server_groups_set{false};
ChannelId assignment_channel_group_channel{0};
std::shared_ptr<groups::ChannelGroup> assignment_channel_group{};
std::shared_ptr<BasicChannel> server_channel;
ChannelId last_server_channel{0};
};
enum struct PermissionResetResult {
SUCCESS
};
class PermissionService {
public:
explicit PermissionService(vserver::VirtualServerBase*);
~PermissionService() = default;
bool initialize(std::string& /* error */);
bool load_data(std::string& /* error */);
void unload_data();
PermissionResetResult reset_server_permissions();
permission::v2::PermissionFlaggedValue calculate_client_permission(
permission::PermissionType,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted,
std::shared_ptr<CalculateCache> cache = nullptr
);
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_client_permissions(
const std::deque<permission::PermissionType>&,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted,
std::shared_ptr<CalculateCache> cache = nullptr
);
private:
vserver::VirtualServerBase* virtual_server_{nullptr};
[[nodiscard]] ServerId get_server_id() const;
void load_group_assignments(const std::shared_ptr<CalculateCache>&);
void load_channel_assignment(const std::shared_ptr<CalculateCache>&, ChannelId /* channel id */);
void load_server_channel(const std::shared_ptr<CalculateCache>&, ChannelId /* channel id */);
};
}

View File

@ -0,0 +1,5 @@
//
// Created by WolverinDEV on 07/03/2020.
//
#include "VirtualServerBroadcastService.h"

View File

@ -0,0 +1,31 @@
#pragma once
#include <memory>
#include <deque>
#include <Properties.h>
namespace ts::server {
class ConnectedClient;
}
namespace ts::server::vserver {
class VirtualServerBase;
class BroadcastService {
public:
explicit BroadcastService(VirtualServerBase*);
bool client_updated(const std::shared_ptr<ConnectedClient>& /* client */,
const std::deque<std::shared_ptr<property::PropertyDescription>>& /* keys */, bool /* notify_client */ = true);
inline bool client_updated(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool notify_client = 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));
return this->client_updated(client, _keys, notify_client);
};
private:
VirtualServerBase* virtual_server_;
};
}

View File

@ -0,0 +1,15 @@
//
// Created by WolverinDEV on 04/03/2020.
//
#include "./VirtualServerInformation.h"
#include "../vserver/VirtualServerBase.h"
using namespace ts::server::vserver;
InformationService::InformationService(ts::server::vserver::VirtualServerBase *handle) : virtual_server_{handle} {}
InformationService::~InformationService() {}
float InformationService::averagePing() {
return -2;
}

View File

@ -0,0 +1,18 @@
#pragma once
namespace ts::server::vserver {
class VirtualServerBase;
class InformationService {
public:
explicit InformationService(VirtualServerBase*);
~InformationService();
[[nodiscard]] float averagePing();
[[nodiscard]] float averagePacketLoss();
[[nodiscard]] bool could_default_create_channel();
private:
VirtualServerBase* virtual_server_;
};
}

View File

@ -0,0 +1,33 @@
//
// Created by WolverinDEV on 03/03/2020.
//
#include <Error.h>
#include "DefaultServer.h"
using namespace ts::server::vserver;
VirtualServerStartResult DefaultServer::start_server_virtual_(ts::rwshared_lock<ts::rw_mutex> &lock, std::string &error) {
if(!lock.auto_lock_exclusive()) {
error = "failed to lock server state";
return VirtualServerStartResult::CUSTOM;
}
if(auto result = DefaultServer::start_server_virtual_(lock, error); result != VirtualServerStartResult::SUCCESS)
return result;
//TODO: Load anything
this->status_ = VirtualServerStatus::VIRTUAL;
return VirtualServerStartResult::SUCCESS;
}
VirtualServerStartResult DefaultServer::start_server_(ts::rwshared_lock<ts::rw_mutex> &lock, std::string &string) {
return VirtualServerStartResult::SERVER_IS_DEFAULT;
}
void DefaultServer::stop_server_(ts::rwshared_lock<ts::rw_mutex> &lock) {
VirtualServerBase::stop_server_(lock);
/* TODO: Save server states? Because we can't really shut down. */
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "./VirtualServerBase.h"
namespace ts::server::vserver {
class DefaultServer : public VirtualServerBase {
public:
private:
protected:
VirtualServerStartResult start_server_virtual_(ts::rwshared_lock<ts::rw_mutex> &lock, std::string &string) override;
VirtualServerStartResult start_server_(ts::rwshared_lock<ts::rw_mutex> &lock, std::string &string) override;
void stop_server_(ts::rwshared_lock<ts::rw_mutex> &lock) override;
};
}

Some files were not shown because too many files have changed in this diff Show More