Compare commits
10 Commits
new-groups
...
1.5.0-old
Author | SHA1 | Date |
---|---|---|
root | d2ba1b4eee | |
WolverinDEV | d6f483a019 | |
WolverinDEV | 58666b8906 | |
WolverinDEV | c002be7307 | |
WolverinDEV | 60a1c34dc9 | |
WolverinDEV | 799df15ace | |
WolverinDEV | 7926a26091 | |
WolverinDEV | 6172628247 | |
WolverinDEV | 3a5c152b3c | |
WolverinDEV | be4a99dcd7 |
|
@ -1 +1 @@
|
|||
Subproject commit 9a26231c1f6c44f799419f87a3cac37a55425bdb
|
||||
Subproject commit cba03a6316db76fdf056a960ee509ad20542ea44
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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{};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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()){
|
||||
|
|
|
@ -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};
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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){
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */);
|
||||
};
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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 */
|
||||
}
|
|
@ -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 */);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 */);
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
}
|
|
@ -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};
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "PermissionManager.h"
|
||||
#include "PermissionRegister.h"
|
||||
#include "Definitions.h"
|
||||
|
||||
namespace ts {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>()));
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
|
||||
{
|
||||
|
|
|
@ -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 */);
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()){
|
||||
|
|
|
@ -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 *);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}));
|
|
@ -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{};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
// Created by WolverinDEV on 07/03/2020.
|
||||
//
|
||||
|
||||
#include "UDPServer.h"
|
|
@ -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();
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 */);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
// Created by WolverinDEV on 07/03/2020.
|
||||
//
|
||||
|
||||
#include "VirtualServerBroadcastService.h"
|
|
@ -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_;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_;
|
||||
};
|
||||
}
|
|
@ -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. */
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue